To ease testing of the code I'm responsible for testing, I'm refactoring my team's LogHandler
.
They had something like this:
type LogHandlerSt struct {
path string
fileName string
projectName string
}
var (
//Instance The project instance of log handler.
Instance LogHandlerSt
//OnResponseError A function that can be set to be called on response error.
OnResponseError func(context interface{}, response *http.Response)
//includeStack Will include all lines with the strings below in the debug stack.
includeStack = []string{"apiserver_sdk", "ezdineserver_sdk"}
)
//CreateLogHandler Creates and assigns a log handler instance for use by the project.
func CreateLogHandler(path string, fileName string, projectName string) LogHandlerSt {
Instance = LogHandlerSt{path: path, fileName: fileName, projectName: projectName}.
fmt.Println("Log files are in \"" + path + "\"")
return Instance
}
//log Logs a message given the type of message and the message itself.
func (handler *LogHandlerSt) log(logType string, errMsg string) {
fmt.Println(errMsg)
logFile, err := os.OpenFile(handler.path+"/"+handler.fileName+" "+time.Now().Format("2006-01-02")+".log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0777)
if err != nil {
fmt.Println(err)
}
defer logFile.Close()
if _, err = logFile.WriteString(`
` + logType + " " + time.Now().Format("2006-01-02 15:04:05.000") + `: ` + errMsg + "
"); err != nil {
fmt.Println(err)
}
logFile.Sync()
}
//GetProjectNames Gets the projects to use for the log handler instance. Usually used for the debug stack.
func (handler *LogHandlerSt) GetProjectNames() []string {
var projectNames = []string{handler.projectName}
projectNames = append(projectNames, includeStack...)
return projectNames
}
//LogError Logs an error given information.
func (handler *LogHandlerSt) LogError(errMsg string) {
handler.log("Error", errMsg)
}
//LogWarning Logs a warning given information.
func (handler *LogHandlerSt) LogWarning(errMsg string) {
handler.log("Warn", errMsg)
}
//LogInfo Logs some information.
func (handler *LogHandlerSt) LogInfo(errMsg string) {
handler.log("Info", errMsg)
}
Many methods in the code base under test use these functions, on Instance
. To remedy this, I introduced a struct Logger
thusly:
// Logger provides a generic implementation of the required methods.
// Provided for testing purposes
type Logger interface {
LogError(errMessage string)
LogWarning(errMessage string)
LogInfo(errMessage string)
}
Instincts, especially from Java, told me to change declaration of that Instance
to a Logger
. This, of course, did nothing, as Logger
was an instance whereas the methods were on a *Logger
. So, I changed to Instance *Logger
and refactored CreateLogHandler
to take care of the compile-time error I just created:
//CreateLogHandler Creates and assigns a log handler instance for use by the project.
func CreateLogHandler(path string, fileName string, projectName string) LogHandlerSt {
Instance = &LogHandlerSt{path: path, fileName: fileName, projectName: projectName}
fmt.Println("Log files are in \"" + path + "\"")
return Instance
}
I get told:
cannot use LogHandlerSt literal (type *LogHandlerSt) as type *Logger in assignment:
*Logger is pointer to interface, not interface
WHY NOT, and what can I do about this?
" as
Logger
was an instance whereas the methods were on a*Logger
".
Stop right there. Logger
is an interface. Methods are "not on an interface". Concrete implementations have a concrete receiver type, which will be *LogHandlerSt
, not *Logger
, nor Logger
. Also LogHandlerSt
does not implement Logger
, only *LogHandlerSt
as methods are defined with pointer receiver.
If you introduce Logger
, this is how you may use it:
var Instance Logger
func CreateLogHandler(path string, fileName string, projectName string) Logger {
Instance = &LogHandlerSt{
path: path,
fileName: fileName,
projectName: projectName
}
fmt.Println("Log files are in \"" + path + "\"")
return Instance
}
Since only *LogHandlerSt
implements Logger
, you have to assign a pointer to Instance
.
Note that you should rarely (if ever) use a pointer to interface (such as *Logger
). Instead the pointer to the concrete type should be wrapped in an interface value.