There are some singleton implementations in the Go standard library, such as the default
net.DefaultResolver in the
log package, which provide convenient methods, but there are times when we need to do some customization and need to change these objects.
There are even times when we need to change specific methods of the standard library, and the conventional means do not work, we must use some “hacking” methods.
In the process of developing the project, I also encountered some needs to change the default behavior of the standard library, so I did some exploration in this area and put together this article for the benefit of readers.
If you want to implement your own logging library (and there are many, many logging libraries in the Go ecosystem), you may want to “intercept” the standard library’s default Log, so that your code, or third-party code that outputs logs from the standard library
log, can be fed through your own logging library.
In fact, the standard library
log default Logger is defined as follows:
var std = New(os.Stderr, "", LstdFlags) , std implements a
Logger that outputs to
os.Stderr . The Go standard library Logger is not an interface, so you may not be able to do much customization per se, but at least you can change the destination of the log output, for example from the standard err output to a log file. Here
std is the unoutput variable, but the standard library provides the
func SetOutput(w io.Writer) method to change the output destination.
So it seems that the log library is ok, at least it exposes a way to change the customization, but there are many cases where the standard library does not provide a way to customize, or a way to not facilitate customization.
When I was implementing a project these days, I came across a situation where a machine had multiple IPs.
It is not uncommon for a machine to have multiple IP addresses configured, so when you connect to other TCP servers on this machine, which IP address is used locally? As serverfault has asked, by default Linux will choose the local address of the same subnet as the server according to the subnet classification, but if multiple IP addresses are configured in the same subnet, then Linux will choose the “primary” IP address of this subnet as the local Ip address to connect to the server.
In my project, there are many network connections, such as to mysql, to clickhouse, to third-party HTTP API services, to Kafka, to Redis, and so on. Unfortunately, when using third-party libraries like go-sql-driver/mysql, go-redis, the local IP address selected by Linux is not the local IP address I expect, resulting in permission verification failures and inability to connect.
go-redis , both are TCP connections established based on
net.Dial or `net.
RegisterDialContext for customizing
Dialer field for customization, and you can specify local IP address if you want by customizing `net.
This is a good, traditional method, the only irritation is that for each type I need to address, access mysql, access redis, access kafka, access third party libraries, access the server …… , is there a one-and-done approach?
bouk/monkey is a rather “hacky” technique that dynamically replaces method implementations with
JMP new functions at runtime to achieve method replacement at runtime. We often use it to
Mock some methods during unit testing, and it works very well. This time, I’m going to try to use it to replace all `net.
Of course, agiledragon implements agiledragon/gomonkey based on its principle to facilitate calls, but it does not support temporary recovery of the original function at present. Chunhui Cao has implemented cch123/supermonkey based on it, which can replace the unexported function by parsing the symbol table to get the function pointer, which can be said to be more powerful. In this article, we still use the original bouk/monkey, because for me, the function is enough.
Don’t use bouk/monkey to do evil.
You can use bouk/monkey to replace the
net.Dialer.DialContext functions of the standard library to use the local IP address when establishing a TCP connection. This way, either mysql’s library, redis’s library, or any other third-party library that is based on the
net.Dialer.DialContext function will use the method we replaced.
The relevant code is also very simple, as shown below, and comments have been added to the code:
After replacing these two methods, even if a new
net.Dailer object is created afterwards, it will be executed using the replaced methods.
If your program has concurrent calls to Dial or DialContext, you need to add locks.
Thus, we have solved the problem of specifying local IP addresses to create TCP connections once and for all, without changing the code of the standard library and without customizing Dial methods individually.
Similarly, you can change the standard library’s
This is the implementation of the standard library for domain name resolution, and supports Go’s own resolution implementation and CGO queries. It is a struct in itself, not an interface, so although it is a singleton object, you usually don’t have much customization possibilities. For example, when calling the
LookupIP method, you want to use a protocol of your own to return a list of IPs, rather than querying local files or DNS servers, you basically have no way to do that. But with bouk/monkey, you can change the
LookupIP method so that you can customize it.
So, bouk/monkey can be used not only to mock objects and methods in unit tests, but also to replace functions that are not routinely changed while the application is running.