mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
allow unsetting a log level through subcommand and add admin page for settng log level
This commit is contained in:
parent
4202fbe108
commit
6cbe4d5d37
8 changed files with 212 additions and 7 deletions
14
ctl.go
14
ctl.go
|
@ -613,16 +613,20 @@ func servectlcmd(ctx context.Context, log *mlog.Log, ctl *ctl, xcmd *string, shu
|
||||||
/* protocol:
|
/* protocol:
|
||||||
> "setloglevels"
|
> "setloglevels"
|
||||||
> pkg
|
> pkg
|
||||||
> level
|
> level (if empty, log level for pkg will be unset)
|
||||||
< "ok" or error
|
< "ok" or error
|
||||||
*/
|
*/
|
||||||
pkg := ctl.xread()
|
pkg := ctl.xread()
|
||||||
levelstr := ctl.xread()
|
levelstr := ctl.xread()
|
||||||
level, ok := mlog.Levels[levelstr]
|
if levelstr == "" {
|
||||||
if !ok {
|
mox.Conf.LogLevelRemove(pkg)
|
||||||
ctl.xerror("bad level")
|
} else {
|
||||||
|
level, ok := mlog.Levels[levelstr]
|
||||||
|
if !ok {
|
||||||
|
ctl.xerror("bad level")
|
||||||
|
}
|
||||||
|
mox.Conf.LogLevelSet(pkg, level)
|
||||||
}
|
}
|
||||||
mox.Conf.SetLogLevel(pkg, level)
|
|
||||||
ctl.xwriteok()
|
ctl.xwriteok()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
2
doc.go
2
doc.go
|
@ -146,6 +146,8 @@ By default, a single log level applies to all logging in mox. But for each
|
||||||
smtpserver, smtpclient, queue, imapserver, spf, dkim, dmarc, junk, message,
|
smtpserver, smtpclient, queue, imapserver, spf, dkim, dmarc, junk, message,
|
||||||
etc.
|
etc.
|
||||||
|
|
||||||
|
Specify a pkg and an empty level to clear the configured level for a package.
|
||||||
|
|
||||||
Valid labels: error, info, debug, trace, traceauth, tracedata.
|
Valid labels: error, info, debug, trace, traceauth, tracedata.
|
||||||
|
|
||||||
usage: mox loglevels [level [pkg]]
|
usage: mox loglevels [level [pkg]]
|
||||||
|
|
|
@ -1462,3 +1462,26 @@ func (Admin) QueueDrop(ctx context.Context, id int64) {
|
||||||
}
|
}
|
||||||
xcheckf(ctx, err, "drop message from queue")
|
xcheckf(ctx, err, "drop message from queue")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogLevels returns the current log levels.
|
||||||
|
func (Admin) LogLevels(ctx context.Context) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
for pkg, level := range mox.Conf.LogLevels() {
|
||||||
|
m[pkg] = level.String()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLevelSet sets a log level for a package.
|
||||||
|
func (Admin) LogLevelSet(ctx context.Context, pkg string, levelStr string) {
|
||||||
|
level, ok := mlog.Levels[levelStr]
|
||||||
|
if !ok {
|
||||||
|
xcheckf(ctx, errors.New("unknown"), "lookup level")
|
||||||
|
}
|
||||||
|
mox.Conf.LogLevelSet(pkg, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLevelRemove removes a log level for a package, which cannot be the empty string.
|
||||||
|
func (Admin) LogLevelRemove(ctx context.Context, pkg string) {
|
||||||
|
mox.Conf.LogLevelRemove(pkg)
|
||||||
|
}
|
||||||
|
|
113
http/admin.html
113
http/admin.html
|
@ -260,6 +260,7 @@ const index = async () => {
|
||||||
dom.br(),
|
dom.br(),
|
||||||
dom.h2('Configuration'),
|
dom.h2('Configuration'),
|
||||||
dom.div(dom.a('See configuration', attr({href: '#config'}))),
|
dom.div(dom.a('See configuration', attr({href: '#config'}))),
|
||||||
|
dom.div(dom.a('Log levels', attr({href: '#loglevels'}))),
|
||||||
footer,
|
footer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -280,6 +281,116 @@ const config = async () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loglevels = async () => {
|
||||||
|
const loglevels = await api.LogLevels()
|
||||||
|
|
||||||
|
const levels = ['error', 'info', 'debug', 'trace', 'traceauth', 'tracedata']
|
||||||
|
|
||||||
|
let form, fieldset, pkg, level
|
||||||
|
|
||||||
|
const page = document.getElementById('page')
|
||||||
|
dom._kids(page,
|
||||||
|
crumbs(
|
||||||
|
crumblink('Mox Admin', '#'),
|
||||||
|
'Log levels',
|
||||||
|
),
|
||||||
|
dom.p('Note: changing a log level here only changes it for the current process. When mox restarts, it sets the log levels from the configuration file. Change mox.conf to keep the changes.'),
|
||||||
|
dom.table(
|
||||||
|
dom.thead(
|
||||||
|
dom.tr(
|
||||||
|
dom.th('Package', attr({title: 'Log levels can be configured per package. E.g. smtpserver, imapserver, dkim, dmarc, tlsrpt, etc.'})),
|
||||||
|
dom.th('Level', attr({title: 'If you set the log level to "trace", imap and smtp protocol transcripts will be logged. Sensitive authentication is replaced with "***" unless the level is >= "traceauth". Data is masked with "..." unless the level is "tracedata".'})),
|
||||||
|
dom.th('Action'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dom.tbody(
|
||||||
|
Object.entries(loglevels).map(t => {
|
||||||
|
let lvl
|
||||||
|
return dom.tr(
|
||||||
|
dom.td(t[0] || '(default)'),
|
||||||
|
dom.td(
|
||||||
|
lvl=dom.select(levels.map(l => dom.option(l, t[1] === l ? attr({selected: ''}) : []))),
|
||||||
|
),
|
||||||
|
dom.td(
|
||||||
|
dom.button('Save', attr({title: 'Set new log level for package.'}), async function click(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
e.target.disabled = true
|
||||||
|
await api.LogLevelSet(t[0], lvl.value)
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
e.target.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: reload just the current loglevels
|
||||||
|
}),
|
||||||
|
' ',
|
||||||
|
dom.button('Remove', attr({title: 'Remove this log level, the default log level will apply.'}), t[0] === '' ? attr({disabled: ''}) : [], async function click(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
e.target.disabled = true
|
||||||
|
await api.LogLevelRemove(t[0])
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
e.target.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: reload just the current loglevels
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dom.br(),
|
||||||
|
dom.h2('Add log level setting'),
|
||||||
|
form=dom.form(
|
||||||
|
async function submit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
fieldset.disabled = true
|
||||||
|
try {
|
||||||
|
await api.LogLevelSet(pkg.value, level.value)
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
fieldset.disabled = false
|
||||||
|
}
|
||||||
|
form.reset()
|
||||||
|
window.location.reload() // todo: reload just the current loglevels
|
||||||
|
},
|
||||||
|
fieldset=dom.fieldset(
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
'Package',
|
||||||
|
dom.br(),
|
||||||
|
pkg=dom.input(attr({required: ''})),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
'Level',
|
||||||
|
dom.br(),
|
||||||
|
level=dom.select(
|
||||||
|
attr({required: ''}),
|
||||||
|
levels.map(l => dom.option(l, l === 'debug' ? attr({selected: ''}) : [])),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.button('Add'),
|
||||||
|
),
|
||||||
|
dom.br(),
|
||||||
|
dom.p('Suggestions for packages: autotls dkim dmarc dmarcdb dns dnsbl dsn http imapserver iprev junk message metrics mox moxio mtasts mtastsdb publicsuffix queue sendmail serve smtpserver spf store subjectpass tlsrpt tlsrptdb updates'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const box = (color, ...l) => [
|
const box = (color, ...l) => [
|
||||||
dom.div(
|
dom.div(
|
||||||
style({
|
style({
|
||||||
|
@ -1438,6 +1549,8 @@ const init = async () => {
|
||||||
await index()
|
await index()
|
||||||
} else if (h === 'config') {
|
} else if (h === 'config') {
|
||||||
await config()
|
await config()
|
||||||
|
} else if (h === 'loglevels') {
|
||||||
|
await loglevels()
|
||||||
} else if (h === 'accounts') {
|
} else if (h === 'accounts') {
|
||||||
await accounts()
|
await accounts()
|
||||||
} else if (t[0] === 'accounts' && t.length === 2) {
|
} else if (t[0] === 'accounts' && t.length === 2) {
|
||||||
|
|
|
@ -577,6 +577,52 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Returns": []
|
"Returns": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "LogLevels",
|
||||||
|
"Docs": "LogLevels returns the current log levels.",
|
||||||
|
"Params": [],
|
||||||
|
"Returns": [
|
||||||
|
{
|
||||||
|
"Name": "r0",
|
||||||
|
"Typewords": [
|
||||||
|
"{}",
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "LogLevelSet",
|
||||||
|
"Docs": "LogLevelSet sets a log level for a package.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "pkg",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "levelStr",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "LogLevelRemove",
|
||||||
|
"Docs": "LogLevelRemove removes a log level for a package, which cannot be the empty string.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "pkg",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Sections": [],
|
"Sections": [],
|
||||||
|
|
2
main.go
2
main.go
|
@ -745,6 +745,8 @@ By default, a single log level applies to all logging in mox. But for each
|
||||||
smtpserver, smtpclient, queue, imapserver, spf, dkim, dmarc, junk, message,
|
smtpserver, smtpclient, queue, imapserver, spf, dkim, dmarc, junk, message,
|
||||||
etc.
|
etc.
|
||||||
|
|
||||||
|
Specify a pkg and an empty level to clear the configured level for a package.
|
||||||
|
|
||||||
Valid labels: error, info, debug, trace, traceauth, tracedata.
|
Valid labels: error, info, debug, trace, traceauth, tracedata.
|
||||||
`
|
`
|
||||||
args := c.Parse()
|
args := c.Parse()
|
||||||
|
|
|
@ -38,6 +38,10 @@ var Logfmt bool
|
||||||
|
|
||||||
type Level int
|
type Level int
|
||||||
|
|
||||||
|
func (l Level) String() string {
|
||||||
|
return LevelStrings[l]
|
||||||
|
}
|
||||||
|
|
||||||
var LevelStrings = map[Level]string{
|
var LevelStrings = map[Level]string{
|
||||||
LevelPrint: "print",
|
LevelPrint: "print",
|
||||||
LevelFatal: "fatal",
|
LevelFatal: "fatal",
|
||||||
|
|
|
@ -66,10 +66,10 @@ type AccountDestination struct {
|
||||||
Destination config.Destination
|
Destination config.Destination
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogLevel sets a new log level for pkg. An empty pkg sets the default log
|
// LogLevelSet sets a new log level for pkg. An empty pkg sets the default log
|
||||||
// value that is used if no explicit log level is configured for a package.
|
// value that is used if no explicit log level is configured for a package.
|
||||||
// This change is ephemeral, no config file is changed.
|
// This change is ephemeral, no config file is changed.
|
||||||
func (c *Config) SetLogLevel(pkg string, level mlog.Level) {
|
func (c *Config) LogLevelSet(pkg string, level mlog.Level) {
|
||||||
c.logMutex.Lock()
|
c.logMutex.Lock()
|
||||||
defer c.logMutex.Unlock()
|
defer c.logMutex.Unlock()
|
||||||
l := c.copyLogLevels()
|
l := c.copyLogLevels()
|
||||||
|
@ -79,6 +79,17 @@ func (c *Config) SetLogLevel(pkg string, level mlog.Level) {
|
||||||
mlog.SetConfig(c.Log)
|
mlog.SetConfig(c.Log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogLevelRemove removes a configured log level for a package.
|
||||||
|
func (c *Config) LogLevelRemove(pkg string) {
|
||||||
|
c.logMutex.Lock()
|
||||||
|
defer c.logMutex.Unlock()
|
||||||
|
l := c.copyLogLevels()
|
||||||
|
delete(l, pkg)
|
||||||
|
c.Log = l
|
||||||
|
xlog.Print("log level cleared", mlog.Field("pkg", pkg))
|
||||||
|
mlog.SetConfig(c.Log)
|
||||||
|
}
|
||||||
|
|
||||||
// copyLogLevels returns a copy of c.Log, for modifications.
|
// copyLogLevels returns a copy of c.Log, for modifications.
|
||||||
// must be called with log lock held.
|
// must be called with log lock held.
|
||||||
func (c *Config) copyLogLevels() map[string]mlog.Level {
|
func (c *Config) copyLogLevels() map[string]mlog.Level {
|
||||||
|
|
Loading…
Reference in a new issue