mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 08:23:48 +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:
|
||||
> "setloglevels"
|
||||
> pkg
|
||||
> level
|
||||
> level (if empty, log level for pkg will be unset)
|
||||
< "ok" or error
|
||||
*/
|
||||
pkg := ctl.xread()
|
||||
levelstr := ctl.xread()
|
||||
level, ok := mlog.Levels[levelstr]
|
||||
if !ok {
|
||||
ctl.xerror("bad level")
|
||||
if levelstr == "" {
|
||||
mox.Conf.LogLevelRemove(pkg)
|
||||
} else {
|
||||
level, ok := mlog.Levels[levelstr]
|
||||
if !ok {
|
||||
ctl.xerror("bad level")
|
||||
}
|
||||
mox.Conf.LogLevelSet(pkg, level)
|
||||
}
|
||||
mox.Conf.SetLogLevel(pkg, level)
|
||||
ctl.xwriteok()
|
||||
|
||||
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,
|
||||
etc.
|
||||
|
||||
Specify a pkg and an empty level to clear the configured level for a package.
|
||||
|
||||
Valid labels: error, info, debug, trace, traceauth, tracedata.
|
||||
|
||||
usage: mox loglevels [level [pkg]]
|
||||
|
|
|
@ -1462,3 +1462,26 @@ func (Admin) QueueDrop(ctx context.Context, id int64) {
|
|||
}
|
||||
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.h2('Configuration'),
|
||||
dom.div(dom.a('See configuration', attr({href: '#config'}))),
|
||||
dom.div(dom.a('Log levels', attr({href: '#loglevels'}))),
|
||||
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) => [
|
||||
dom.div(
|
||||
style({
|
||||
|
@ -1438,6 +1549,8 @@ const init = async () => {
|
|||
await index()
|
||||
} else if (h === 'config') {
|
||||
await config()
|
||||
} else if (h === 'loglevels') {
|
||||
await loglevels()
|
||||
} else if (h === 'accounts') {
|
||||
await accounts()
|
||||
} else if (t[0] === 'accounts' && t.length === 2) {
|
||||
|
|
|
@ -577,6 +577,52 @@
|
|||
}
|
||||
],
|
||||
"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": [],
|
||||
|
|
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,
|
||||
etc.
|
||||
|
||||
Specify a pkg and an empty level to clear the configured level for a package.
|
||||
|
||||
Valid labels: error, info, debug, trace, traceauth, tracedata.
|
||||
`
|
||||
args := c.Parse()
|
||||
|
|
|
@ -38,6 +38,10 @@ var Logfmt bool
|
|||
|
||||
type Level int
|
||||
|
||||
func (l Level) String() string {
|
||||
return LevelStrings[l]
|
||||
}
|
||||
|
||||
var LevelStrings = map[Level]string{
|
||||
LevelPrint: "print",
|
||||
LevelFatal: "fatal",
|
||||
|
|
|
@ -66,10 +66,10 @@ type AccountDestination struct {
|
|||
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.
|
||||
// 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()
|
||||
defer c.logMutex.Unlock()
|
||||
l := c.copyLogLevels()
|
||||
|
@ -79,6 +79,17 @@ func (c *Config) SetLogLevel(pkg string, level mlog.Level) {
|
|||
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.
|
||||
// must be called with log lock held.
|
||||
func (c *Config) copyLogLevels() map[string]mlog.Level {
|
||||
|
|
Loading…
Reference in a new issue