|
@ -0,0 +1,396 @@ |
|
|
|
|
|
--- |
|
|
|
|
|
date: "2019-04-02T17:06:00+01:00" |
|
|
|
|
|
title: "Advanced: Logging Configuration" |
|
|
|
|
|
slug: "logging-configuration" |
|
|
|
|
|
weight: 55 |
|
|
|
|
|
toc: true |
|
|
|
|
|
draft: false |
|
|
|
|
|
menu: |
|
|
|
|
|
sidebar: |
|
|
|
|
|
parent: "advanced" |
|
|
|
|
|
name: "Logging Configuration" |
|
|
|
|
|
weight: 55 |
|
|
|
|
|
identifier: "logging-configuration" |
|
|
|
|
|
--- |
|
|
|
|
|
|
|
|
|
|
|
# Logging Configuration |
|
|
|
|
|
|
|
|
|
|
|
The logging framework has been revamped in Gitea 1.9.0. |
|
|
|
|
|
|
|
|
|
|
|
## Log Groups |
|
|
|
|
|
|
|
|
|
|
|
The fundamental thing to be aware of in Gitea is that there are several |
|
|
|
|
|
log groups: |
|
|
|
|
|
|
|
|
|
|
|
* The "Default" logger |
|
|
|
|
|
* The Macaron logger |
|
|
|
|
|
* The Router logger |
|
|
|
|
|
* The Access logger |
|
|
|
|
|
* The XORM logger |
|
|
|
|
|
* A logger called the `GitLogger` which is used during hooks. |
|
|
|
|
|
|
|
|
|
|
|
There is also the go log logger. |
|
|
|
|
|
|
|
|
|
|
|
### The go log logger |
|
|
|
|
|
|
|
|
|
|
|
Go provides its own extremely basic logger in the `log` package, |
|
|
|
|
|
however, this is not sufficient for our purposes as it does not provide |
|
|
|
|
|
a way of logging at multiple levels, nor does it provide a good way of |
|
|
|
|
|
controlling where these logs are logged except through setting of a |
|
|
|
|
|
writer. |
|
|
|
|
|
|
|
|
|
|
|
We have therefore redirected this logger to our Default logger, and we |
|
|
|
|
|
will log anything that is logged using the go logger at the INFO level. |
|
|
|
|
|
|
|
|
|
|
|
### The "Default" logger |
|
|
|
|
|
|
|
|
|
|
|
Calls to `log.Info`, `log.Debug`, `log.Error` etc. from the `code.gitea.io/gitea/modules/log` package will log to this logger. |
|
|
|
|
|
|
|
|
|
|
|
You can configure the outputs of this logger by setting the `MODE` |
|
|
|
|
|
value in the `[log]` section of the configuration. |
|
|
|
|
|
|
|
|
|
|
|
Each output sublogger is configured in a separate `[log.sublogger]` |
|
|
|
|
|
section, but there are certain default values. These will not be inherited from the `[log]` section: |
|
|
|
|
|
|
|
|
|
|
|
* `FLAGS` is `stdflags` (Equal to |
|
|
|
|
|
`date,time,medfile,shortfuncname,levelinitial`) |
|
|
|
|
|
* `FILE_NAME` will default to `%(ROOT_PATH)/gitea.log` |
|
|
|
|
|
* `EXPRESSION` will default to `""` |
|
|
|
|
|
* `PREFIX` will default to `""` |
|
|
|
|
|
|
|
|
|
|
|
The provider type of the sublogger can be set using the `MODE` value in |
|
|
|
|
|
its subsection, but will default to the name. This allows you to have |
|
|
|
|
|
multiple subloggers that will log to files. |
|
|
|
|
|
|
|
|
|
|
|
### The "Macaron" logger |
|
|
|
|
|
|
|
|
|
|
|
By default Macaron will log to its own go `log` instance. This writes |
|
|
|
|
|
to `os.Stdout`. You can redirect this log to a Gitea configurable logger |
|
|
|
|
|
through setting the `ENABLE_MACARON_REDIRECT` setting in the `[log]` |
|
|
|
|
|
section which you can configure the outputs of by setting the `MACARON` |
|
|
|
|
|
value in the `[log]` section of the configuration. `MACARON` defaults |
|
|
|
|
|
to `file` if unset. |
|
|
|
|
|
|
|
|
|
|
|
Each output sublogger for this logger is configured in |
|
|
|
|
|
`[log.sublogger.macaron]` sections. There are certain default values |
|
|
|
|
|
which will not be inherited from the `[log]` or relevant |
|
|
|
|
|
`[log.sublogger]` sections: |
|
|
|
|
|
|
|
|
|
|
|
* `FLAGS` is `stdflags` (Equal to |
|
|
|
|
|
`date,time,medfile,shortfuncname,levelinitial`) |
|
|
|
|
|
* `FILE_NAME` will default to `%(ROOT_PATH)/macaron.log` |
|
|
|
|
|
* `EXPRESSION` will default to `""` |
|
|
|
|
|
* `PREFIX` will default to `""` |
|
|
|
|
|
|
|
|
|
|
|
NB: You can redirect the macaron logger to send its events to the gitea |
|
|
|
|
|
log using the value: `MACARON = ,` |
|
|
|
|
|
|
|
|
|
|
|
### The "Router" logger |
|
|
|
|
|
|
|
|
|
|
|
There are two types of Router log. By default Macaron send its own |
|
|
|
|
|
router log which will be directed to Macaron's go `log`, however if you |
|
|
|
|
|
`ENABLE_MACARON_REDIRECT` you will enable Gitea's router log. You can |
|
|
|
|
|
disable both types of Router log by setting `DISABLE_ROUTER_LOG`. |
|
|
|
|
|
|
|
|
|
|
|
If you enable the redirect, you can configure the outputs of this |
|
|
|
|
|
router log by setting the `ROUTER` value in the `[log]` section of the |
|
|
|
|
|
configuration. `ROUTER` will default to `console` if unset. The Gitea |
|
|
|
|
|
Router logs the same data as the Macaron log but has slightly different |
|
|
|
|
|
coloring. It logs at the `Info` level by default, but this can be |
|
|
|
|
|
changed if desired by setting the `ROUTER_LOG_LEVEL` value. |
|
|
|
|
|
|
|
|
|
|
|
Each output sublogger for this logger is configured in |
|
|
|
|
|
`[log.sublogger.router]` sections. There are certain default values |
|
|
|
|
|
which will not be inherited from the `[log]` or relevant |
|
|
|
|
|
`[log.sublogger]` sections: |
|
|
|
|
|
|
|
|
|
|
|
* `FILE_NAME` will default to `%(ROOT_PATH)/router.log` |
|
|
|
|
|
* `FLAGS` defaults to `date,time` |
|
|
|
|
|
* `EXPRESSION` will default to `""` |
|
|
|
|
|
* `PREFIX` will default to `""` |
|
|
|
|
|
|
|
|
|
|
|
NB: You can redirect the router logger to send its events to the Gitea |
|
|
|
|
|
log using the value: `ROUTER = ,` |
|
|
|
|
|
|
|
|
|
|
|
### The "Access" logger |
|
|
|
|
|
|
|
|
|
|
|
The Access logger is a new logger for version 1.9. It provides a NCSA |
|
|
|
|
|
Common Log compliant log format. It's highly configurable but caution |
|
|
|
|
|
should be taken when changing its template. The main benefit of this |
|
|
|
|
|
logger is that Gitea can now log accesses in a standard log format so |
|
|
|
|
|
standard tools may be used. |
|
|
|
|
|
|
|
|
|
|
|
You can enable this logger using `ENABLE_ACCESS_LOG`. Its outputs are |
|
|
|
|
|
configured by setting the `ACCESS` value in the `[log]` section of the |
|
|
|
|
|
configuration. `ACCESS` defaults to `file` if unset. |
|
|
|
|
|
|
|
|
|
|
|
Each output sublogger for this logger is configured in |
|
|
|
|
|
`[log.sublogger.access]` sections. There are certain default values |
|
|
|
|
|
which will not be inherited from the `[log]` or relevant |
|
|
|
|
|
`[log.sublogger]` sections: |
|
|
|
|
|
|
|
|
|
|
|
* `FILE_NAME` will default to `%(ROOT_PATH)/access.log` |
|
|
|
|
|
* `FLAGS` defaults to `` or None |
|
|
|
|
|
* `EXPRESSION` will default to `""` |
|
|
|
|
|
* `PREFIX` will default to `""` |
|
|
|
|
|
|
|
|
|
|
|
If desired the format of the Access logger can be changed by changing |
|
|
|
|
|
the value of the `ACCESS_LOG_TEMPLATE`. |
|
|
|
|
|
|
|
|
|
|
|
NB: You can redirect the access logger to send its events to the Gitea |
|
|
|
|
|
log using the value: `ACCESS = ,` |
|
|
|
|
|
|
|
|
|
|
|
#### The ACCESS_LOG_TEMPLATE |
|
|
|
|
|
|
|
|
|
|
|
This value represent a go template. It's default value is: |
|
|
|
|
|
|
|
|
|
|
|
`{{.Ctx.RemoteAddr}} - {{.Identity}} {{.Start.Format "[02/Jan/2006:15:04:05 -0700]" }} "{{.Ctx.Req.Method}} {{.Ctx.Req.RequestURI}} {{.Ctx.Req.Proto}}" {{.ResponseWriter.Status}} {{.ResponseWriter.Size}} "{{.Ctx.Req.Referer}}\" \"{{.Ctx.Req.UserAgent}}"` |
|
|
|
|
|
|
|
|
|
|
|
The template is passed following options: |
|
|
|
|
|
|
|
|
|
|
|
* `Ctx` is the `macaron.Context` |
|
|
|
|
|
* `Identity` is the `SignedUserName` or `"-"` if the user is not logged |
|
|
|
|
|
in |
|
|
|
|
|
* `Start` is the start time of the request |
|
|
|
|
|
* `ResponseWriter` is the `macaron.ResponseWriter` |
|
|
|
|
|
|
|
|
|
|
|
Caution must be taken when changing this template as it runs outside of |
|
|
|
|
|
the standard panic recovery trap. The template should also be as simple |
|
|
|
|
|
as it runs for every request. |
|
|
|
|
|
|
|
|
|
|
|
### The "XORM" logger |
|
|
|
|
|
|
|
|
|
|
|
The XORM logger is a long-standing logger that exists to collect XORM |
|
|
|
|
|
log events. It is enabled by default but can be switched off by setting |
|
|
|
|
|
`ENABLE_XORM_LOG` to `false` in the `[log]` section. Its outputs are |
|
|
|
|
|
configured by setting the `XORM` value in the `[log]` section of the |
|
|
|
|
|
configuration. `XORM` defaults to `,` if unset, meaning it is redirected |
|
|
|
|
|
to the main Gitea log. |
|
|
|
|
|
|
|
|
|
|
|
XORM will log SQL events by default. This can be changed by setting |
|
|
|
|
|
the `LOG_SQL` value to `false` in the `[database]` section. |
|
|
|
|
|
|
|
|
|
|
|
Each output sublogger for this logger is configured in |
|
|
|
|
|
`[log.sublogger.xorm]` sections. There are certain default values |
|
|
|
|
|
which will not be inherited from the `[log]` or relevant |
|
|
|
|
|
`[log.sublogger]` sections: |
|
|
|
|
|
|
|
|
|
|
|
* `FILE_NAME` will default to `%(ROOT_PATH)/xorm.log` |
|
|
|
|
|
* `FLAGS` defaults to `date,time` |
|
|
|
|
|
* `EXPRESSION` will default to `""` |
|
|
|
|
|
* `PREFIX` will default to `""` |
|
|
|
|
|
|
|
|
|
|
|
### The Hook and Serv "GitLoggers" |
|
|
|
|
|
|
|
|
|
|
|
These are less well defined loggers. Essentially these should only be |
|
|
|
|
|
used within Gitea's subsystems and cannot be configured at present. |
|
|
|
|
|
|
|
|
|
|
|
They will write log files in: |
|
|
|
|
|
|
|
|
|
|
|
* `%(ROOT_PATH)/hooks/pre-receive.log` |
|
|
|
|
|
* `%(ROOT_PATH)/hooks/update.log` |
|
|
|
|
|
* `%(ROOT_PATH)/hooks/post-receive.log` |
|
|
|
|
|
* `%(ROOT_PATH)/serv.log` |
|
|
|
|
|
* `%(ROOT_PATH)/http.log` |
|
|
|
|
|
|
|
|
|
|
|
In the future these logs may be rationalised. |
|
|
|
|
|
|
|
|
|
|
|
## Log outputs |
|
|
|
|
|
|
|
|
|
|
|
Gitea provides 4 possible log outputs: |
|
|
|
|
|
|
|
|
|
|
|
* `console` - Log to `os.Stdout` or `os.Stderr` |
|
|
|
|
|
* `file` - Log to a file |
|
|
|
|
|
* `conn` - Log to a keep-alive TCP connection |
|
|
|
|
|
* `smtp` - Log via email |
|
|
|
|
|
|
|
|
|
|
|
Certain configuration is common to all modes of log output: |
|
|
|
|
|
|
|
|
|
|
|
* `LEVEL` is the lowest level that this output will log. This value |
|
|
|
|
|
is inherited from `[log]` and in the case of the non-default loggers |
|
|
|
|
|
from `[log.sublogger]`. |
|
|
|
|
|
* `STACKTRACE_LEVEL` is the lowest level that this output will print |
|
|
|
|
|
a stacktrace. This value is inherited. |
|
|
|
|
|
* `MODE` is the mode of the log output. It will default to the sublogger |
|
|
|
|
|
name. Thus `[log.console.macaron]` will default to `MODE = console`. |
|
|
|
|
|
* `COLORIZE` will default to `true` for `file` and `console` as |
|
|
|
|
|
described, otherwise it will default to `false`. |
|
|
|
|
|
|
|
|
|
|
|
### Non-inherited default values |
|
|
|
|
|
|
|
|
|
|
|
There are several values which are not inherited as described above but |
|
|
|
|
|
rather default to those specific to type of logger, these are: |
|
|
|
|
|
`EXPRESSION`, `FLAGS`, `PREFIX` and `FILE_NAME`. |
|
|
|
|
|
|
|
|
|
|
|
#### `EXPRESSION` |
|
|
|
|
|
|
|
|
|
|
|
`EXPRESSION` represents a regular expression that log events must match to be logged by the sublogger. Either the log message, (with colors removed), must match or the `longfilename:linenumber:functionname` must match. NB: the whole message or string doesn't need to completely match. |
|
|
|
|
|
|
|
|
|
|
|
Please note this expression will be run in the sublogger's goroutine |
|
|
|
|
|
not the logging event subroutine. Therefore it can be complicated. |
|
|
|
|
|
|
|
|
|
|
|
#### `FLAGS` |
|
|
|
|
|
|
|
|
|
|
|
`FLAGS` represents the preceding logging context information that is |
|
|
|
|
|
printed before each message. It is a comma-separated string set. The order of values does not matter. |
|
|
|
|
|
|
|
|
|
|
|
Possible values are: |
|
|
|
|
|
|
|
|
|
|
|
* `none` or `,` - No flags. |
|
|
|
|
|
* `date` - the date in the local time zone: `2009/01/23`. |
|
|
|
|
|
* `time` - the time in the local time zone: `01:23:23`. |
|
|
|
|
|
* `microseconds` - microsecond resolution: `01:23:23.123123`. Assumes |
|
|
|
|
|
time. |
|
|
|
|
|
* `longfile` - full file name and line number: `/a/b/c/d.go:23`. |
|
|
|
|
|
* `shortfile` - final file name element and line number: `d.go:23`. |
|
|
|
|
|
* `funcname` - function name of the caller: `runtime.Caller()`. |
|
|
|
|
|
* `shortfuncname` - last part of the function name. Overrides |
|
|
|
|
|
`funcname`. |
|
|
|
|
|
* `utc` - if date or time is set, use UTC rather than the local time |
|
|
|
|
|
zone. |
|
|
|
|
|
* `levelinitial` - Initial character of the provided level in brackets eg. `[I]` for info. |
|
|
|
|
|
* `level` - Provided level in brackets `[INFO]` |
|
|
|
|
|
* `medfile` - Last 20 characters of the filename - equivalent to |
|
|
|
|
|
`shortfile,longfile`. |
|
|
|
|
|
* `stdflags` - Equivalent to `date,time,medfile,shortfuncname,levelinitial` |
|
|
|
|
|
|
|
|
|
|
|
### Console mode |
|
|
|
|
|
|
|
|
|
|
|
For loggers in console mode, `COLORIZE` will default to `true` if not |
|
|
|
|
|
on windows, or the windows terminal can be set into ANSI mode or is a |
|
|
|
|
|
cygwin or Msys pipe. |
|
|
|
|
|
|
|
|
|
|
|
If `STDERR` is set to `true` the logger will use `os.Stderr` instead of |
|
|
|
|
|
`os.Stdout`. |
|
|
|
|
|
|
|
|
|
|
|
### File mode |
|
|
|
|
|
|
|
|
|
|
|
The `FILE_NAME` defaults as described above. If set it will be relative |
|
|
|
|
|
to the provided `ROOT_PATH` in the master `[log]` section. |
|
|
|
|
|
|
|
|
|
|
|
Other values: |
|
|
|
|
|
|
|
|
|
|
|
* `LOG_ROTATE`: **true**: Rotate the log files. |
|
|
|
|
|
* `MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file, 28 represents 256Mb. |
|
|
|
|
|
* `DAILY_ROTATE`: **true**: Rotate logs daily. |
|
|
|
|
|
* `MAX_DAYS`: **7**: Delete the log file after n days |
|
|
|
|
|
* NB: `COLORIZE`: will default to `true` if not on windows. |
|
|
|
|
|
* `COMPRESS`: **true**: Compress old log files by default with gzip |
|
|
|
|
|
* `COMPRESSION_LEVEL`: **-1**: Compression level |
|
|
|
|
|
|
|
|
|
|
|
### Conn mode |
|
|
|
|
|
|
|
|
|
|
|
* `RECONNECT_ON_MSG`: **false**: Reconnect host for every single message. |
|
|
|
|
|
* `RECONNECT`: **false**: Try to reconnect when connection is lost. |
|
|
|
|
|
* `PROTOCOL`: **tcp**: Set the protocol, either "tcp", "unix" or "udp". |
|
|
|
|
|
* `ADDR`: **:7020**: Sets the address to connect to. |
|
|
|
|
|
|
|
|
|
|
|
### SMTP mode |
|
|
|
|
|
|
|
|
|
|
|
It is not recommended to use this logger to send general logging |
|
|
|
|
|
messages. However, you could perhaps set this logger to work on `FATAL`. |
|
|
|
|
|
|
|
|
|
|
|
* `USER`: User email address to send from. |
|
|
|
|
|
* `PASSWD`: Password for the smtp server. |
|
|
|
|
|
* `HOST`: **127.0.0.1:25**: The SMTP host to connect to. |
|
|
|
|
|
* `RECEIVERS`: Email addresses to send to. |
|
|
|
|
|
* `SUBJECT`: **Diagnostic message from Gitea** |
|
|
|
|
|
|
|
|
|
|
|
## Default Configuration |
|
|
|
|
|
|
|
|
|
|
|
The default empty configuration is equivalent to: |
|
|
|
|
|
|
|
|
|
|
|
```ini |
|
|
|
|
|
[log] |
|
|
|
|
|
ROOT_PATH = %(GITEA_WORK_DIR)/log |
|
|
|
|
|
MODE = console |
|
|
|
|
|
LEVEL = Info |
|
|
|
|
|
STACKTRACE_LEVEL = None |
|
|
|
|
|
REDIRECT_MACARON_LOG = false |
|
|
|
|
|
ENABLE_ACCESS_LOG = false |
|
|
|
|
|
ENABLE_XORM_LOG = true |
|
|
|
|
|
XORM = , |
|
|
|
|
|
|
|
|
|
|
|
[log.console] |
|
|
|
|
|
MODE = console |
|
|
|
|
|
LEVEL = %(LEVEL) |
|
|
|
|
|
STACKTRACE_LEVEL = %(STACKTRACE_LEVEL) |
|
|
|
|
|
FLAGS = stdflags |
|
|
|
|
|
PREFIX = |
|
|
|
|
|
COLORIZE = true # Or false if your windows terminal cannot color |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
This is equivalent to sending all logs to the console, with default go log being sent to the console log too. |
|
|
|
|
|
|
|
|
|
|
|
## Log colorization |
|
|
|
|
|
|
|
|
|
|
|
Logs to the console will be colorized by default when not running on |
|
|
|
|
|
Windows. Terminal sniffing will occur on Windows and if it is |
|
|
|
|
|
determined that we are running on a terminal capable of color we will |
|
|
|
|
|
colorize. |
|
|
|
|
|
|
|
|
|
|
|
Further, on *nix it is becoming common to have file logs that are |
|
|
|
|
|
colored by default. Therefore file logs will be colorised by default |
|
|
|
|
|
when not running on Windows. |
|
|
|
|
|
|
|
|
|
|
|
You can switch on or off colorization by using the `COLORIZE` value. |
|
|
|
|
|
|
|
|
|
|
|
From a development point of view. If you write |
|
|
|
|
|
`log.Info("A %s string", "formatted")` the `formatted` part of the log |
|
|
|
|
|
message will be Bolded on colorized logs. |
|
|
|
|
|
|
|
|
|
|
|
You can change this by either rendering the formatted string yourself. |
|
|
|
|
|
Or you can wrap the value in a `log.ColoredValue` struct. |
|
|
|
|
|
|
|
|
|
|
|
The `log.ColoredValue` struct contains a pointer to value, a pointer to |
|
|
|
|
|
string of bytes which should represent a color and second set of reset |
|
|
|
|
|
bytes. Pointers were chosen to prevent copying of large numbers of |
|
|
|
|
|
values. There are several helper methods: |
|
|
|
|
|
|
|
|
|
|
|
* `log.NewColoredValue` takes a value and 0 or more color attributes |
|
|
|
|
|
that represent the color. If 0 are provided it will default to a cached |
|
|
|
|
|
bold. Note, it is recommended that color bytes constructed from |
|
|
|
|
|
attributes should be cached if this is a commonly used log message. |
|
|
|
|
|
* `log.NewColoredValuePointer` takes a pointer to a value, and |
|
|
|
|
|
0 or more color attributes that represent the color. |
|
|
|
|
|
* `log.NewColoredValueBytes` takes a value and a pointer to an array |
|
|
|
|
|
of bytes representing the color. |
|
|
|
|
|
|
|
|
|
|
|
These functions will not double wrap a `log.ColoredValue`. They will |
|
|
|
|
|
also set the ResetBytes to the cached resetBytes. |
|
|
|
|
|
|
|
|
|
|
|
Be careful not to change the contents of resetBytes or boldBytes as this |
|
|
|
|
|
will break rendering of logging elsewhere. You have been warned. |
|
|
|
|
|
|
|
|
|
|
|
## Log Spoofing protection |
|
|
|
|
|
|
|
|
|
|
|
In order to protect the logs from being spoofed with cleverly |
|
|
|
|
|
constructed messages. Newlines are now prefixed with a tab and control |
|
|
|
|
|
characters except those used in an ANSI CSI are escaped with a |
|
|
|
|
|
preceding `\` and their octal value. |
|
|
|
|
|
|
|
|
|
|
|
## Creating a new named logger group |
|
|
|
|
|
|
|
|
|
|
|
Should a developer wish to create a new named logger, `NEWONE`. It is |
|
|
|
|
|
recommended to add an `ENABLE_NEWONE_LOG` value to the `[log]` |
|
|
|
|
|
section, and to add a new `NEWONE` value for the modes. |
|
|
|
|
|
|
|
|
|
|
|
A function like `func newNewOneLogService()` is recommended to manage |
|
|
|
|
|
construction of the named logger. e.g. |
|
|
|
|
|
|
|
|
|
|
|
```go |
|
|
|
|
|
func newNewoneLogService() { |
|
|
|
|
|
EnableNewoneLog = Cfg.Section("log").Key("ENABLE_NEWONE_LOG").MustBool(false) |
|
|
|
|
|
Cfg.Section("log").Key("NEWONE").MustString("file") // or console? or "," if you want to send this to default logger by default |
|
|
|
|
|
if EnableNewoneLog { |
|
|
|
|
|
options := newDefaultLogOptions() |
|
|
|
|
|
options.filename = filepath.Join(LogRootPath, "newone.log") |
|
|
|
|
|
options.flags = "stdflags" |
|
|
|
|
|
options.bufferLength = Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000) |
|
|
|
|
|
generateNamedLogger("newone", options) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
You should then add `newOneLogService` to `NewServices()` in |
|
|
|
|
|
`modules/setting/setting.go` |