feat: move to slog for logging

This commit is contained in:
kolaente
2025-07-19 16:21:35 +02:00
parent 36df5d8c41
commit ca83ad1f98
33 changed files with 490 additions and 264 deletions

View File

@@ -395,6 +395,11 @@
"default_value": "INFO",
"comment": "Change the log level. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG."
},
{
"key": "format",
"default_value": "text",
"comment": "Logging format. Can be either `text` or `structured` to output JSON."
},
{
"key": "database",
"default_value": "off",

3
go.mod
View File

@@ -55,7 +55,6 @@ require (
github.com/mattn/go-sqlite3 v1.14.28
github.com/microcosm-cc/bluemonday v1.0.27
github.com/olekukonko/tablewriter v1.0.8
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.5.0
github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.11.0
@@ -115,8 +114,6 @@ require (
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/huandu/go-clone v1.7.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect

43
go.sum
View File

@@ -17,8 +17,6 @@ github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/ThreeDotsLabs/watermill v1.4.6 h1:rWoXlxdBgUyg/bZ3OO0pON+nESVd9r6tnLTgkZ6CYrU=
github.com/ThreeDotsLabs/watermill v1.4.6/go.mod h1:lBnrLbxOjeMRgcJbv+UiZr8Ylz8RkJ4m6i/VN/Nk+to=
github.com/ThreeDotsLabs/watermill v1.4.7 h1:LiF4wMP400/psRTdHL/IcV1YIv9htHYFggbe2d6cLeI=
github.com/ThreeDotsLabs/watermill v1.4.7/go.mod h1:Ks20MyglVnqjpha1qq0kjaQ+J9ay7bdnjszQ4cW9FMU=
github.com/adlio/trello v1.12.0 h1:JqOE2GFHQ9YtEviRRRSnicSxPbt4WFOxhqXzjMOw8lw=
@@ -98,12 +96,8 @@ github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBv
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/ganigeorgiev/fexpr v0.5.0 h1:XA9JxtTE/Xm+g/JFI6RfZEHSiQlk+1glLvRK1Lpv/Tk=
github.com/ganigeorgiev/fexpr v0.5.0/go.mod h1:RyGiGqmeXhEQ6+mlGdnUleLHgtzzu/VGO2WtJkF5drE=
github.com/getsentry/sentry-go v0.34.0 h1:1FCHBVp8TfSc8L10zqSwXUZNiOSF+10qw4czjarTiY4=
github.com/getsentry/sentry-go v0.34.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/getsentry/sentry-go v0.34.1 h1:HSjc1C/OsnZttohEPrrqKH42Iud0HuLCXpv8cU1pWcw=
github.com/getsentry/sentry-go v0.34.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/getsentry/sentry-go/echo v0.34.0 h1:qjEOup0MJ4qyNKEc/H1XUooIQndka0HOGDMFbLR3x8E=
github.com/getsentry/sentry-go/echo v0.34.0/go.mod h1:kCjZ3/HnI340yMESlyRRYoL/7stfCkuxakhFkrR2nxk=
github.com/getsentry/sentry-go/echo v0.34.1 h1:QmRs8A6SK7YYbc6Dtuyh2RTFd4Fe9v9VH8Ty4h8wC8s=
github.com/getsentry/sentry-go/echo v0.34.1/go.mod h1:4kdQH/69jXiWE7Ve5nwkWa9U4A38FK/Eu/zSQ4tcaHc=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
@@ -136,8 +130,6 @@ github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-testfixtures/testfixtures/v3 v3.16.0 h1:bSjH1206tTSXSQZLtUjLvy6sbSrSXO8lGtcB454QP8Q=
github.com/go-testfixtures/testfixtures/v3 v3.16.0/go.mod h1:fH6IT/2lM5yV5cuTkPkuQv3f6JW+AE9tMUovxUhWUhE=
github.com/go-testfixtures/testfixtures/v3 v3.17.0 h1:oaWVDAOl13JszPM1jQZ2iS6bIBhy43WY3gpTeQEq/IU=
github.com/go-testfixtures/testfixtures/v3 v3.17.0/go.mod h1:HCIVT6p9uKXaCv898IT1iS0My5TF8kF785H4n+6049U=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
@@ -147,13 +139,9 @@ github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYA
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -194,11 +182,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
@@ -256,8 +239,6 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jaswdr/faker/v2 v2.5.0 h1:KUYfnleIZMSHNp/q+rDk7XEuqUUL5FhfT19iTTFqF5o=
github.com/jaswdr/faker/v2 v2.5.0/go.mod h1:ROK8xwQV0hYOLDUtxCQgHGcl10jbVzIvqHxcIDdwY2Q=
github.com/jaswdr/faker/v2 v2.6.0 h1:19MkwsI6I2vrJhYZqsJpspzRb/fDlax2q1iBhPMxGy0=
github.com/jaswdr/faker/v2 v2.6.0/go.mod h1:jZq+qzNQr8/P+5fHd9t3txe2GNPnthrTfohtnJ7B+68=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
@@ -367,8 +348,6 @@ github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hS
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=
github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -381,8 +360,6 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@@ -465,8 +442,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
github.com/swaggo/swag v1.16.5 h1:nMf2fEV1TetMTJb4XzD0Lz7jFfKJmJKGTygEey8NSxM=
github.com/swaggo/swag v1.16.5/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@@ -529,13 +504,9 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -567,8 +538,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
@@ -582,8 +551,6 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -618,8 +585,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
@@ -632,8 +597,6 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -648,13 +611,9 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -673,8 +632,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -137,6 +137,7 @@ const (
LogEnabled Key = `log.enabled`
LogStandard Key = `log.standard`
LogLevel Key = `log.level`
LogFormat Key = `log.format`
LogDatabase Key = `log.database`
LogDatabaseLevel Key = `log.databaselevel`
LogHTTP Key = `log.http`
@@ -400,6 +401,7 @@ func InitDefaultConfig() {
LogEnabled.setDefault(true)
LogStandard.setDefault("stdout")
LogLevel.setDefault("INFO")
LogFormat.setDefault("text")
LogDatabase.setDefault("off")
LogDatabaseLevel.setDefault("WARNING")
LogHTTP.setDefault("stdout")
@@ -537,7 +539,7 @@ func InitConfig() {
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
log.ConfigureLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString())
log.ConfigureStandardLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString(), LogFormat.GetString())
// Load the config file
viper.AddConfigPath(ServiceRootpath.GetString())
@@ -562,7 +564,7 @@ func InitConfig() {
log.Warning(err.Error())
log.Warning("Using default config.")
} else {
log.ConfigureLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString())
log.ConfigureStandardLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString(), LogFormat.GetString())
}
} else {
log.Info("No config file found, using default or config from environment variables.")

View File

@@ -87,7 +87,7 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
}
engine.SetTZDatabase(loc)
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), config.LogDatabaseLevel.GetString())
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), config.LogDatabaseLevel.GetString(), config.LogFormat.GetString())
engine.SetLogger(logger)
x = engine

View File

@@ -53,7 +53,7 @@ func CreateTestEngine() (engine *xorm.Engine, err error) {
}
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), "DEBUG")
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), "DEBUG", config.LogFormat.GetString())
logger.ShowSQL(os.Getenv("TESTS_VERBOSE") == "1")
engine.SetLogger(logger)
engine.SetTZLocation(config.GetTimeZone())

View File

@@ -51,7 +51,7 @@ func (m *messageHandleFailedError) Error() string {
// InitEvents sets up everything needed to work with events
func InitEvents() (err error) {
logger := log.NewWatermillLogger(config.LogEnabled.GetBool(), config.LogEvents.GetString(), config.LogEventsLevel.GetString())
logger := log.NewWatermillLogger(config.LogEnabled.GetBool(), config.LogEvents.GetString(), config.LogEventsLevel.GetString(), config.LogFormat.GetString())
router, err := message.NewRouter(
message.RouterConfig{},

View File

@@ -143,7 +143,7 @@ func (f *File) Delete(s *xorm.Session) (err error) {
var perr *os.PathError
if errors.As(err, &perr) {
// Don't fail when removing the file failed
log.Errorf("Error deleting file %d: %w", f.ID, err)
log.Errorf("Error deleting file %d: %s", f.ID, err)
return s.Commit()
}

View File

@@ -19,10 +19,15 @@ package files
import (
"os"
"testing"
"code.vikunja.io/api/pkg/log"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
InitTests()
os.Exit(m.Run())
}

186
pkg/log/echo_logger.go Normal file
View File

@@ -0,0 +1,186 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package log
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
)
type EchoLogger struct {
logger *slog.Logger
writer io.Writer
}
// NewEchoLogger creates and initializes a new echo logger
func NewEchoLogger(configLogEnabled bool, configLogEcho string, configLogFormat string) echo.Logger {
handler, writer := makeLogHandler(configLogEnabled, configLogEcho, "DEBUG", configLogFormat)
echoLogger := &EchoLogger{
logger: slog.New(handler).With("component", "http"),
writer: writer,
}
return echoLogger
}
func (e *EchoLogger) Output() io.Writer {
return e.writer
}
func (e *EchoLogger) SetOutput(_ io.Writer) {
}
func (e *EchoLogger) Prefix() string {
return "http"
}
func (e *EchoLogger) SetPrefix(_ string) {
}
func (e *EchoLogger) Level() log.Lvl {
return log.DEBUG
}
func (e *EchoLogger) SetLevel(_ log.Lvl) {
}
func (e *EchoLogger) SetHeader(_ string) {
}
func (e *EchoLogger) Print(i ...interface{}) {
e.logger.Info(fmt.Sprint(i...))
}
func (e *EchoLogger) Printf(format string, args ...interface{}) {
e.logger.Info(fmt.Sprintf(format, args...))
}
func (e *EchoLogger) Printj(j log.JSON) {
if b, err := json.Marshal(j); err == nil {
e.logger.Info(string(b))
}
}
func (e *EchoLogger) Debug(i ...interface{}) {
e.logger.Debug(fmt.Sprint(i...))
}
func (e *EchoLogger) Debugf(format string, args ...interface{}) {
e.logger.Debug(fmt.Sprintf(format, args...))
}
func (e *EchoLogger) Debugj(j log.JSON) {
if b, err := json.Marshal(j); err == nil {
e.logger.Debug(string(b))
}
}
func (e *EchoLogger) Info(i ...interface{}) {
e.logger.Info(fmt.Sprint(i...))
}
func (e *EchoLogger) Infof(format string, args ...interface{}) {
e.logger.Info(fmt.Sprintf(format, args...))
}
func (e *EchoLogger) Infoj(j log.JSON) {
if b, err := json.Marshal(j); err == nil {
e.logger.Info(string(b))
}
}
func (e *EchoLogger) Warn(i ...interface{}) {
e.logger.Warn(fmt.Sprint(i...))
}
func (e *EchoLogger) Warnf(format string, args ...interface{}) {
e.logger.Warn(fmt.Sprintf(format, args...))
}
func (e *EchoLogger) Warnj(j log.JSON) {
if b, err := json.Marshal(j); err == nil {
e.logger.Warn(string(b))
}
}
func (e *EchoLogger) Error(i ...interface{}) {
e.logger.Error(fmt.Sprint(i...))
}
func (e *EchoLogger) Errorf(format string, args ...interface{}) {
e.logger.Error(fmt.Sprintf(format, args...))
}
func (e *EchoLogger) Errorj(j log.JSON) {
if b, err := json.Marshal(j); err == nil {
e.logger.Error(string(b))
}
}
func (e *EchoLogger) Fatal(i ...interface{}) {
e.logger.Error(fmt.Sprint(i...))
os.Exit(1)
}
func (e *EchoLogger) Fatalj(j log.JSON) {
if b, err := json.Marshal(j); err == nil {
e.logger.Error(string(b))
}
os.Exit(1)
}
func (e *EchoLogger) Fatalf(format string, args ...interface{}) {
e.logger.Error(fmt.Sprintf(format, args...))
os.Exit(1)
}
func (e *EchoLogger) Panic(i ...interface{}) {
msg := fmt.Sprint(i...)
e.logger.Error(msg)
panic(msg)
}
func (e *EchoLogger) Panicj(j log.JSON) {
if b, err := json.Marshal(j); err == nil {
msg := string(b)
e.logger.Error(msg)
panic(msg)
}
}
func (e *EchoLogger) Panicf(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
e.logger.Error(msg)
panic(msg)
}
// EnableColor enables color output
func (e *EchoLogger) EnableColor() {
// This is a no-op for our slog implementation
}
// DisableColor disables color output
func (e *EchoLogger) DisableColor() {
// This is a no-op for our slog implementation
}

View File

@@ -17,27 +17,15 @@
package log
import (
"fmt"
"io"
"log/slog"
"os"
"strings"
"time"
"github.com/op/go-logging"
)
// ErrFmt holds the format for all the console logging
const ErrFmt = `${time_rfc3339}: ${level} ` + "\t" + `▶ ${prefix} ${short_file}:${line}`
// WebFmt holds the format for all logging related to web requests
const WebFmt = `${time_rfc3339}: WEB ` + "\t" + `▶ ${remote_ip} ${id} ${method} ${status} ${uri} ${latency_human} - ${user_agent}`
// Fmt is the general log format
const Fmt = `%{color}%{time:` + time.RFC3339 + `}: %{level}` + "\t" + `▶ %{id:03x}%{color:reset} %{message}`
const logModule = `vikunja`
// loginstance is the instance of the logger which is used under the hood to log
var logInstance = logging.MustGetLogger(logModule)
// logInstance is the instance of the logger which is used under the hood to log
var logInstance *slog.Logger
// logpath is the path in which log files will be written.
// This value is a mere fallback for other modules that could but shouldn't be used before calling ConfigureLogger
@@ -45,45 +33,74 @@ var logPath = "."
// InitLogger initializes the global log handler
func InitLogger() {
// This show correct caller functions
logInstance.ExtraCalldepth = 1
// Init with stdout and INFO as default format and level
logBackend := logging.NewLogBackend(os.Stdout, "", 0)
backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(Fmt+"\n"))
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(logging.INFO, logModule)
logInstance.SetBackend(backendLeveled)
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
logInstance = slog.New(handler)
}
// ConfigureLogger configures the global log handler
func ConfigureLogger(configLogEnabled bool, configLogStandard string, configLogPath string, configLogLevel string) {
lvl := strings.ToUpper(configLogLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Fatalf("Error setting standard log level %s: %s", lvl, err.Error())
func makeLogHandler(enabled bool, output string, level string, format string) (slog.Handler, io.Writer) {
var slogLevel slog.Level
switch strings.ToUpper(level) {
case "CRITICAL", "ERROR":
slogLevel = slog.LevelError
case "WARNING":
slogLevel = slog.LevelWarn
case "NOTICE", "INFO":
slogLevel = slog.LevelInfo
case "DEBUG":
slogLevel = slog.LevelDebug
default:
slogLevel = slog.LevelInfo
}
logPath = configLogPath
// The backend is the part which actually handles logging the log entries somewhere.
var backend logging.Backend
backend = &NoopBackend{}
if configLogEnabled && configLogStandard != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogStandard, "standard"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(Fmt+"\n"))
format = strings.ToLower(format)
if format == "" {
format = "text"
}
if format != "text" && format != "structured" {
Fatalf("invalid log format %s", format)
}
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, logModule)
writer := io.Discard
if enabled && output != "off" {
writer = getLogWriter(output, "standard")
}
logInstance.SetBackend(backendLeveled)
return createHandler(writer, slogLevel, format), writer
}
// createHandler creates a consistent slog handler for all loggers
func createHandler(writer io.Writer, level slog.Level, format string) slog.Handler {
handlerOpts := &slog.HandlerOptions{Level: level}
if strings.ToLower(format) == "structured" {
return slog.NewJSONHandler(writer, handlerOpts)
}
return slog.NewTextHandler(writer, handlerOpts)
}
// NewHTTPLogger creates and initializes a new HTTP logger
func NewHTTPLogger(enabled bool, output string, format string) *slog.Logger {
handler, _ := makeLogHandler(enabled, output, "DEBUG", format)
return slog.New(handler).With("component", "http")
}
// ConfigureStandardLogger configures the global log handler
func ConfigureStandardLogger(enabled bool, output string, path string, level string, format string) {
handler, _ := makeLogHandler(enabled, output, level, format)
logInstance = slog.New(handler)
logPath = path
}
// wrapLogger is used for libraries requiring a Debugf method.
type wrapLogger struct{}
func (wrapLogger) Debugf(format string, args ...interface{}) {
logInstance.Debug(fmt.Sprintf(format, args...))
}
// GetLogWriter returns the writer to where the normal log goes, depending on the config
func GetLogWriter(logfmt string, logfile string) (writer io.Writer) {
func getLogWriter(logfmt string, logfile string) (writer io.Writer) {
writer = os.Stdout // Set the default case to prevent nil pointer panics
switch logfmt {
case "file":
@@ -106,68 +123,72 @@ func GetLogWriter(logfmt string, logfile string) (writer io.Writer) {
}
// GetLogger returns the logging instance. DO NOT USE THIS TO LOG STUFF.
func GetLogger() *logging.Logger {
return logInstance
// GetLogger returns a logger which can be used by external libraries expecting a Debugf method.
// It only implements Debugf and forwards to the global logger.
func GetLogger() interface{ Debugf(string, ...interface{}) } {
return wrapLogger{}
}
// The following functions are to be used as an "eye-candy", so one can just write log.Error() instead of log.Log.Error()
// Debug is for debug messages
func Debug(args ...interface{}) {
logInstance.Debug(args...)
logInstance.Debug(fmt.Sprint(args...))
}
// Debugf is for debug messages
func Debugf(format string, args ...interface{}) {
logInstance.Debugf(format, args...)
logInstance.Debug(fmt.Sprintf(format, args...))
}
// Info is for info messages
func Info(args ...interface{}) {
logInstance.Info(args...)
logInstance.Info(fmt.Sprint(args...))
}
// Infof is for info messages
func Infof(format string, args ...interface{}) {
logInstance.Infof(format, args...)
logInstance.Info(fmt.Sprintf(format, args...))
}
// Error is for error messages
func Error(args ...interface{}) {
logInstance.Error(args...)
logInstance.Error(fmt.Sprint(args...))
}
// Errorf is for error messages
func Errorf(format string, args ...interface{}) {
logInstance.Errorf(format, args...)
logInstance.Error(fmt.Sprintf(format, args...))
}
// Warning is for warning messages
func Warning(args ...interface{}) {
logInstance.Warning(args...)
logInstance.Warn(fmt.Sprint(args...))
}
// Warningf is for warning messages
func Warningf(format string, args ...interface{}) {
logInstance.Warningf(format, args...)
logInstance.Warn(fmt.Sprintf(format, args...))
}
// Critical is for critical messages
func Critical(args ...interface{}) {
logInstance.Critical(args...)
logInstance.Error(fmt.Sprint(args...))
}
// Criticalf is for critical messages
func Criticalf(format string, args ...interface{}) {
logInstance.Criticalf(format, args...)
logInstance.Error(fmt.Sprintf(format, args...))
}
// Fatal is for fatal messages
func Fatal(args ...interface{}) {
logInstance.Fatal(args...)
logInstance.Error(fmt.Sprint(args...))
os.Exit(1)
}
// Fatalf is for fatal messages
func Fatalf(format string, args ...interface{}) {
logInstance.Fatalf(format, args...)
logInstance.Error(fmt.Sprintf(format, args...))
os.Exit(1)
}

View File

@@ -17,75 +17,39 @@
package log
import (
"strings"
"time"
"fmt"
"log/slog"
"github.com/op/go-logging"
"github.com/wneessen/go-mail/log"
maillog "github.com/wneessen/go-mail/log"
)
type MailLogger struct {
logger *logging.Logger
level log.Level
logger *slog.Logger
}
const mailFormat = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ [MAIL] %{id:03x}%{color:reset} %{message}`
const mailLogModule = `vikunja_mail`
// NewMailLogger creates and initializes a new mail logger
func NewMailLogger(configLogEnabled bool, configLogMail string, configLogMailLevel string) log.Logger {
lvl := strings.ToUpper(configLogMailLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Criticalf("Error setting mail log level %s: %s", lvl, err.Error())
}
func NewMailLogger(configLogEnabled bool, configLogMail string, configLogMailLevel string, configLogFormat string) maillog.Logger {
handler, _ := makeLogHandler(configLogEnabled, configLogMail, configLogMailLevel, configLogFormat)
mailLogger := &MailLogger{
logger: logging.MustGetLogger(mailLogModule),
}
var backend logging.Backend
backend = &NoopBackend{}
if configLogEnabled && configLogMail != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogMail, "mail"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(mailFormat+"\n"))
}
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, mailLogModule)
mailLogger.logger.SetBackend(backendLeveled)
switch level {
case logging.CRITICAL:
case logging.ERROR:
mailLogger.level = log.LevelError
case logging.WARNING:
mailLogger.level = log.LevelWarn
case logging.NOTICE:
case logging.INFO:
mailLogger.level = log.LevelInfo
case logging.DEBUG:
mailLogger.level = log.LevelDebug
default:
mailLogger.level = 0
logger: slog.New(handler).With("component", "mail"),
}
return mailLogger
}
func (m *MailLogger) Debugf(l log.Log) {
m.logger.Debugf(l.Format, l.Messages...)
func (m *MailLogger) Debugf(l maillog.Log) {
m.logger.Debug(fmt.Sprintf(l.Format, l.Messages...))
}
func (m *MailLogger) Infof(l log.Log) {
m.logger.Infof(l.Format, l.Messages...)
func (m *MailLogger) Infof(l maillog.Log) {
m.logger.Info(fmt.Sprintf(l.Format, l.Messages...))
}
func (m *MailLogger) Warnf(l log.Log) {
m.logger.Warningf(l.Format, l.Messages...)
func (m *MailLogger) Warnf(l maillog.Log) {
m.logger.Warn(fmt.Sprintf(l.Format, l.Messages...))
}
func (m *MailLogger) Errorf(l log.Log) {
m.logger.Errorf(l.Format, l.Messages...)
func (m *MailLogger) Errorf(l maillog.Log) {
m.logger.Error(fmt.Sprintf(l.Format, l.Messages...))
}

View File

@@ -16,13 +16,11 @@
package log
import (
"github.com/op/go-logging"
)
import "log/slog"
// NoopBackend doesn't log anything. Used in cases where we want to disable logging completely.
type NoopBackend struct{}
func (n *NoopBackend) Log(_ logging.Level, _ int, _ *logging.Record) error {
func (n *NoopBackend) Log(_ slog.Level, _ int, _ *slog.Record) error {
return nil
}

View File

@@ -18,45 +18,23 @@ package log
import (
"fmt"
"strings"
"time"
"log/slog"
"github.com/ThreeDotsLabs/watermill"
"github.com/op/go-logging"
)
const watermillFmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ [EVENTS] %{id:03x}%{color:reset} %{message}`
const watermillLogModule = `vikunja_events`
type WatermillLogger struct {
logger *logging.Logger
logger *slog.Logger
}
// NewXormLogger creates and initializes a new watermill logger
func NewWatermillLogger(configLogEnabled bool, configLogEvents string, configLogEventsLevel string) *WatermillLogger {
lvl := strings.ToUpper(configLogEventsLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Criticalf("Error setting events log level %s: %s", lvl, err.Error())
}
// NewWatermillLogger creates and initializes a new watermill logger
func NewWatermillLogger(configLogEnabled bool, configLogEvents string, configLogEventsLevel string, configLogFormat string) *WatermillLogger {
handler, _ := makeLogHandler(configLogEnabled, configLogEvents, configLogEventsLevel, configLogFormat)
watermillLogger := &WatermillLogger{
logger: logging.MustGetLogger(watermillLogModule),
logger: slog.New(handler).With("component", "events"),
}
var backend logging.Backend
backend = &NoopBackend{}
if configLogEnabled && configLogEvents != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogEvents, "events"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(watermillFmt+"\n"))
}
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, watermillLogModule)
watermillLogger.logger.SetBackend(backendLeveled)
return watermillLogger
}
@@ -75,19 +53,19 @@ func concatFields(fields watermill.LogFields) string {
}
func (w *WatermillLogger) Error(msg string, err error, fields watermill.LogFields) {
w.logger.Errorf("%s: %s, %s", msg, err, concatFields(fields))
w.logger.Error(fmt.Sprintf("%s: %s, %s", msg, err, concatFields(fields)))
}
func (w *WatermillLogger) Info(msg string, fields watermill.LogFields) {
w.logger.Infof("%s, %s", msg, concatFields(fields))
w.logger.Info(fmt.Sprintf("%s, %s", msg, concatFields(fields)))
}
func (w *WatermillLogger) Debug(msg string, fields watermill.LogFields) {
w.logger.Debugf("%s, %s", msg, concatFields(fields))
w.logger.Debug(fmt.Sprintf("%s, %s", msg, concatFields(fields)))
}
func (w *WatermillLogger) Trace(msg string, fields watermill.LogFields) {
w.logger.Debugf("%s, %s", msg, concatFields(fields))
w.logger.Debug(fmt.Sprintf("%s, %s", msg, concatFields(fields)))
}
func (w *WatermillLogger) With(_ watermill.LogFields) watermill.LoggerAdapter {

View File

@@ -17,62 +17,24 @@
package log
import (
"strings"
"time"
"fmt"
"log/slog"
"github.com/op/go-logging"
"xorm.io/xorm/log"
)
// XormFmt defines the format for xorm logging strings
const XormFmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ [DATABASE] %{id:03x}%{color:reset} %{message}`
const xormLogModule = `vikunja_database`
// XormLogger holds an implementation of the xorm logger interface.
type XormLogger struct {
logger *logging.Logger
level log.LogLevel
logger *slog.Logger
showSQL bool
}
// NewXormLogger creates and initializes a new xorm logger
func NewXormLogger(configLogEnabled bool, configLogDatabase string, configLogDatabaseLevel string) *XormLogger {
lvl := strings.ToUpper(configLogDatabaseLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Criticalf("Error setting database log level %s: %s", lvl, err.Error())
}
func NewXormLogger(configLogEnabled bool, configLogDatabase string, configLogDatabaseLevel string, configLogFormat string) *XormLogger {
handler, _ := makeLogHandler(configLogEnabled, configLogDatabase, configLogDatabaseLevel, configLogFormat)
xormLogger := &XormLogger{
logger: logging.MustGetLogger(xormLogModule),
}
var backend logging.Backend
backend = &NoopBackend{}
if configLogEnabled && configLogDatabase != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogDatabase, "database"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(XormFmt+"\n"))
}
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, xormLogModule)
xormLogger.logger.SetBackend(backendLeveled)
switch level {
case logging.CRITICAL:
case logging.ERROR:
xormLogger.level = log.LOG_ERR
case logging.WARNING:
xormLogger.level = log.LOG_WARNING
case logging.NOTICE:
case logging.INFO:
xormLogger.level = log.LOG_INFO
case logging.DEBUG:
xormLogger.level = log.LOG_DEBUG
default:
xormLogger.level = log.LOG_OFF
logger: slog.New(handler).With("component", "database"),
}
xormLogger.showSQL = true
@@ -82,52 +44,51 @@ func NewXormLogger(configLogEnabled bool, configLogDatabase string, configLogDat
// Debug logs a debug string
func (x *XormLogger) Debug(v ...interface{}) {
x.logger.Debug(v...)
x.logger.Debug(fmt.Sprint(v...))
}
// Debugf logs a debug string
func (x *XormLogger) Debugf(format string, v ...interface{}) {
x.logger.Debugf(format, v...)
x.logger.Debug(fmt.Sprintf(format, v...))
}
// Error logs a debug string
func (x *XormLogger) Error(v ...interface{}) {
x.logger.Error(v...)
x.logger.Error(fmt.Sprint(v...))
}
// Errorf logs a debug string
func (x *XormLogger) Errorf(format string, v ...interface{}) {
x.logger.Errorf(format, v...)
x.logger.Error(fmt.Sprintf(format, v...))
}
// Info logs an info string
func (x *XormLogger) Info(v ...interface{}) {
x.logger.Info(v...)
x.logger.Info(fmt.Sprint(v...))
}
// Infof logs an info string
func (x *XormLogger) Infof(format string, v ...interface{}) {
x.logger.Infof(format, v...)
x.logger.Info(fmt.Sprintf(format, v...))
}
// Warn logs a warning string
func (x *XormLogger) Warn(v ...interface{}) {
x.logger.Warning(v...)
x.logger.Warn(fmt.Sprint(v...))
}
// Warnf logs a warning string
func (x *XormLogger) Warnf(format string, v ...interface{}) {
x.logger.Warningf(format, v...)
x.logger.Warn(fmt.Sprintf(format, v...))
}
// Level returns the current set log level
func (x *XormLogger) Level() log.LogLevel {
return x.level
return log.LOG_DEBUG
}
// SetLevel sets the log level
func (x *XormLogger) SetLevel(l log.LogLevel) {
x.level = l
func (x *XormLogger) SetLevel(_ log.LogLevel) {
}
// ShowSQL sets whether to show the log level or not

View File

@@ -56,7 +56,7 @@ func getClient() (*mail.Client, error) {
}),
mail.WithPort(config.MailerPort.GetInt()),
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
mail.WithLogger(log.NewMailLogger(config.LogEnabled.GetBool(), config.LogMail.GetString(), config.LogMailLevel.GetString())),
mail.WithLogger(log.NewMailLogger(config.LogEnabled.GetBool(), config.LogMail.GetString(), config.LogMailLevel.GetString(), config.LogFormat.GetString())),
mail.WithDebugLog(),
}

View File

@@ -98,7 +98,7 @@ func getMessage(opts *Opts) *mail.Msg {
for name, fs := range opts.EmbedFS {
err := m.EmbedFromEmbedFS(name, fs)
if err != nil {
log.Errorf("Error embedding %s via embed.FS into mail: %v", err)
log.Errorf("Error embedding %s via embed.FS into mail: %v", name, err)
}
}

View File

@@ -58,7 +58,7 @@ func initMigration(x *xorm.Engine) *xormigrate.Xormigrate {
})
m := xormigrate.New(x, migrations)
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogEvents.GetString(), config.LogEventsLevel.GetString())
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogEvents.GetString(), config.LogEventsLevel.GetString(), config.LogFormat.GetString())
m.SetLogger(logger)
m.InitSchema(initSchema)
return m

View File

@@ -704,7 +704,7 @@ func (l *UpdateTaskInSavedFilterViews) Handle(msg *message.Message) (err error)
IsErrInvalidTaskFilterConcatinator(err) ||
IsErrInvalidTaskFilterComparator(err) ||
IsErrInvalidTaskField(err) {
log.Debugf("Invalid filter expression for view %d, expression: %s", view.ID, view.Filter)
log.Debugf("Invalid filter expression for view %d, expression: %v", view.ID, view.Filter)
continue
}

View File

@@ -26,6 +26,7 @@ import (
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/i18n"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/user"
)
@@ -54,6 +55,9 @@ func TestMain(m *testing.M) {
setupTime()
// Initialize logger for tests
log.InitLogger()
// Set default config
config.InitDefaultConfig()
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly

View File

@@ -22,12 +22,16 @@ import (
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
user.InitTests()
files.InitTests()
models.SetupTests()

View File

@@ -22,12 +22,16 @@ import (
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
user.InitTests()
files.InitTests()
models.SetupTests()

View File

@@ -24,12 +24,16 @@ import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
// Set default config
config.InitDefaultConfig()
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly

View File

@@ -0,0 +1,32 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package microsofttodo
import (
"os"
"testing"
"code.vikunja.io/api/pkg/log"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
os.Exit(m.Run())
}

View File

@@ -0,0 +1,32 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package todoist
import (
"os"
"testing"
"code.vikunja.io/api/pkg/log"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
os.Exit(m.Run())
}

View File

@@ -610,7 +610,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
// we can't show them individually and the api returns a 404.
buf := bytes.Buffer{}
_, _ = buf.ReadFrom(resp.Body)
log.Debugf("[Todoist Migration] Could not retrieve task details for task %d: %s", i.TaskID, buf.String())
log.Debugf("[Todoist Migration] Could not retrieve task details for task %s: %s", i.TaskID, buf.String())
continue
}

View File

@@ -0,0 +1,32 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package trello
import (
"os"
"testing"
"code.vikunja.io/api/pkg/log"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
os.Exit(m.Run())
}

View File

@@ -24,12 +24,16 @@ import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
// Set default config
config.InitDefaultConfig()
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly

View File

@@ -45,6 +45,9 @@ func SetupTests() {
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
// Set default config
config.InitDefaultConfig()
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly

View File

@@ -53,6 +53,7 @@ package routes
import (
"errors"
"log/slog"
"net/url"
"strings"
"time"
@@ -85,6 +86,30 @@ import (
"github.com/ulule/limiter/v3"
)
// slogHTTPMiddleware creates a custom HTTP logging middleware using slog
func slogHTTPMiddleware(logger *slog.Logger) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return echo.HandlerFunc(func(c echo.Context) error {
start := time.Now()
err := next(c)
req := c.Request()
res := c.Response()
logger.InfoContext(c.Request().Context(),
req.Method+" "+req.RequestURI,
"status", res.Status,
"remote_ip", c.RealIP(),
"latency", time.Since(start),
"user_agent", req.UserAgent(),
)
return err
})
}
}
// NewEcho registers a new Echo instance
func NewEcho() *echo.Echo {
e := echo.New()
@@ -101,11 +126,9 @@ func NewEcho() *echo.Echo {
}
// Logger
if !config.LogEnabled.GetBool() || config.LogHTTP.GetString() != "off" {
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Format: log.WebFmt + "\n",
Output: log.GetLogWriter(config.LogHTTP.GetString(), "http"),
}))
if config.LogEnabled.GetBool() && config.LogHTTP.GetString() != "off" {
httpLogger := log.NewHTTPLogger(config.LogEnabled.GetBool(), config.LogHTTP.GetString(), config.LogFormat.GetString())
e.Use(slogHTTPMiddleware(httpLogger))
}
// panic recover

View File

@@ -19,10 +19,15 @@ package user
import (
"os"
"testing"
"code.vikunja.io/api/pkg/log"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Initialize logger for tests
log.InitLogger()
InitTests()
os.Exit(m.Run())
}

View File

@@ -130,7 +130,7 @@ To define the thing which gets the appropriate auth object, you need to call a m
#### Logging
You can provide your own instance of `logger.Logger` (using [go-logging](https://github.com/op/go-logging)) to the handler.
You can provide your own instance of `slog.Logger` from Go's standard library to the handler.
It will use this instance to log errors which are not better specified or things like users trying to do something they're
not allowed to do and so on.

View File

@@ -29,6 +29,7 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/modules/keyvalue"
@@ -64,6 +65,10 @@ func setupTestEnv() (e *echo.Echo, err error) {
config.InitDefaultConfig()
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
// Initialize logger for tests
log.InitLogger()
// Some tests use the file engine, so we'll need to initialize that
files.InitTests()
user.InitTests()