Logging
Logging is a crucial aspect of every production web application. It helps developers and operations spot bugs, identify performance problems, and analyze outages and security incidents. The data logged depends on the application, typically including the timestamp, log levels (debug, error, info), and contextual information for easy understanding and reproduction.
Base on sirupsen/logrus: Structured, pluggable logging for Go. (github.com)
What to log
It is important to log relevant information for effective logging. Commonly logged data includes:
Timestamp: Indicates when an event occurred or a log was generated.
Log levels: Debug, error, or info levels to categorize the log messages.
Contextual data: Additional information that helps understand the situation and reproduce it easily.
What not to log
In general, you shouldn't log any form of sensitive business data or personally identifiable information. This includes, but is not limited to:
- Password
- Credit card numbers
These restrictions can make logs less useful from an engineering perspective, but they make your application more secure. In many cases, regulations such as GDPR and HIPAA may forbid the logging of personal data.
Functions
// ILogger is an interface for a logger utility.
type ILogger interface {
// Info logs information level messages.
Info(args ...interface{})
// Warn logs warning level messages.
Warn(args ...interface{})
// Debug logs debug level messages.
Debug(args ...interface{})
// Error logs error level messages with an error object.
Error(message error, args ...interface{})
}Example
package services
import (
"fmt"
core "gitlab.finema.co/finema/idin-core"
)
type userService struct {
ctx core.IContext
}
func (s userService) SomeFunc() (*models.User, core.IError) {
// Log an information message
s.ctx.Log().Info("This is an information message", "more data", 99)
// Log a warning message
s.ctx.Log().Warn("This is a warning message")
// Log a debug message
s.ctx.Log().Debug("This is a debug message")
// Log an error message
err := fmt.Errorf("An error occurred")
s.ctx.Log().Error(err, "This is an error message")
return nil, nil
}The boot log
The first lines a process writes say what it is and what it is wired to talk to. NewHTTPServer and NewCronjobContext emit them for you:
{"full_message":"app ready","type":"http","env":"prod","service":"orders",
"sql":["default","replica"],"cache":["default"],"mongo":["reporting"],
"mq":true,"sentry":true,"release":"1.4.5"}
{"full_message":"http server started","addr":"[::]:8080","routes":37}and for a cronjob process, one line per registered job:
{"full_message":"app ready","type":"cronjob","timezone":"Asia/Bangkok","service":"reports"}
{"full_message":"job registered","job":"nightly-report","schedule":"every 1 days at 02:00"}
{"full_message":"cronjob scheduler started","jobs":3}Why this matters. Every capability degrades rather than refusing to start — a service with no CACHE_HOST gets a context with no cache instead of a failure to boot, which is the right trade. The cost is that a config line left out of a deploy is invisible until the first request that needed it, hours later and far from the change that caused it. These lines close that gap: the answer to "is this process even connected to redis?" is in the log before any traffic arrives.
Only configured dependencies are listed. An absent one is left out rather than reported as false — the line says what this deployment has, and a list of everything it does not have is noise on every boot.
Add your own line wherever it helps:
core.LogCapabilities(ctxOptions, core.LogFields{"type": "worker", "queue": "orders"})The route count, not the route table
http server started reports how many routes are registered, not which. The routing table is discoverable from the code, and printing it buries every other line of the boot log — in dev most of all, where debug is the level people actually run.
The listener is also opened before the line is written, so a port already in use fails to launch instead of announcing "started" and then erroring.
Send to sentry
Update the .env file:
SENTRY_DSN=https://[email protected]/xxxxWill automatically send to sentry when Error is called.
Send to graylog
Update the .env file:
LOG_LEVEL=debug|info|warn|error
LOG_HOST=<graylog_ip>
LOG_PORT=<graylog_udp_port>Set the LOG_LEVEL variable to one of the following options: debug, info, warn, or error. This determines the level of logging that will be sent to Graylog. Choose the appropriate level based on the desired verbosity of the logs.