2023-02-08 06:10:51 +03:00
package log
2023-02-04 06:21:50 +03:00
import (
2023-02-06 07:34:27 +03:00
"bytes"
2023-02-09 23:24:12 +03:00
"encoding/json"
2023-02-06 07:34:27 +03:00
"github.com/stretchr/testify/require"
2023-02-18 04:52:48 +03:00
"io"
"log"
2023-02-06 07:34:27 +03:00
"os"
2023-02-18 04:52:48 +03:00
"path/filepath"
2023-02-04 06:21:50 +03:00
"testing"
2023-02-06 07:34:27 +03:00
"time"
2023-02-04 06:21:50 +03:00
)
2023-02-06 07:34:27 +03:00
func TestMain ( m * testing . M ) {
exitCode := m . Run ( )
resetState ( )
2023-02-08 06:10:51 +03:00
SetLevel ( ErrorLevel ) // For other modules!
2023-02-06 07:34:27 +03:00
os . Exit ( exitCode )
}
func TestLog_TagContextFieldFields ( t * testing . T ) {
t . Cleanup ( resetState )
v := & fakeVisitor {
UserID : "u_abc" ,
IP : "1.2.3.4" ,
}
2023-02-07 00:01:32 +03:00
err := & fakeError {
Code : 123 ,
Message : "some error" ,
}
2023-02-06 07:34:27 +03:00
var out bytes . Buffer
2023-02-08 06:10:51 +03:00
SetOutput ( & out )
SetFormat ( JSONFormat )
SetLevelOverride ( "tag" , "stripe" , DebugLevel )
2023-02-09 06:57:10 +03:00
SetLevelOverride ( "number" , "5" , DebugLevel )
2023-02-06 07:34:27 +03:00
2023-02-08 06:10:51 +03:00
Tag ( "mytag" ) .
2023-02-06 07:34:27 +03:00
Field ( "field2" , 123 ) .
Field ( "field1" , "value1" ) .
2023-02-07 20:02:25 +03:00
Time ( time . Unix ( 123 , 999000000 ) . UTC ( ) ) .
2023-02-06 07:34:27 +03:00
Info ( "hi there %s" , "phil" )
2023-02-08 06:10:51 +03:00
Tag ( "not-stripe" ) .
2023-02-06 07:34:27 +03:00
Debug ( "this message will not appear" )
2023-02-08 06:10:51 +03:00
With ( v ) .
Fields ( Context {
2023-02-06 07:34:27 +03:00
"stripe_customer_id" : "acct_123" ,
"stripe_subscription_id" : "sub_123" ,
} ) .
Tag ( "stripe" ) .
2023-02-07 00:01:32 +03:00
Err ( err ) .
2023-02-07 20:02:25 +03:00
Time ( time . Unix ( 456 , 123000000 ) . UTC ( ) ) .
2023-02-06 07:34:27 +03:00
Debug ( "Subscription status %s" , "active" )
2023-02-04 06:21:50 +03:00
2023-02-09 06:57:10 +03:00
Field ( "number" , 5 ) .
Time ( time . Unix ( 777 , 001000000 ) . UTC ( ) ) .
Debug ( "The number 5 is an int, but the level override is a string" )
2023-02-07 20:02:25 +03:00
expected := ` { "time" : "1970-01-01T00:02:03.999Z" , "level" : "INFO" , "message" : "hi there phil" , "field1" : "value1" , "field2" : 123 , "tag" : "mytag" }
{ "time" : "1970-01-01T00:07:36.123Z" , "level" : "DEBUG" , "message" : "Subscription status active" , "error" : "some error" , "error_code" : 123 , "stripe_customer_id" : "acct_123" , "stripe_subscription_id" : "sub_123" , "tag" : "stripe" , "user_id" : "u_abc" , "visitor_ip" : "1.2.3.4" }
2023-02-09 06:57:10 +03:00
{ "time" : "1970-01-01T00:12:57Z" , "level" : "DEBUG" , "message" : "The number 5 is an int, but the level override is a string" , "number" : 5 }
2023-02-06 07:34:27 +03:00
`
require . Equal ( t , expected , out . String ( ) )
}
2023-02-08 06:10:51 +03:00
func TestLog_NoAllocIfNotPrinted ( t * testing . T ) {
t . Cleanup ( resetState )
v := & fakeVisitor {
UserID : "u_abc" ,
IP : "1.2.3.4" ,
}
var out bytes . Buffer
SetOutput ( & out )
SetFormat ( JSONFormat )
// Do not log, do not call contexters (because global level is INFO)
v . contextCalled = false
ev := With ( v )
ev . Debug ( "some message" )
require . False ( t , v . contextCalled )
require . Equal ( t , "" , ev . Timestamp )
require . Equal ( t , Level ( 0 ) , ev . Level )
require . Equal ( t , "" , ev . Message )
require . Nil ( t , ev . fields )
// Logged because info level, contexters called
v . contextCalled = false
ev = With ( v ) . Time ( time . Unix ( 1111 , 0 ) . UTC ( ) )
ev . Info ( "some message" )
require . True ( t , v . contextCalled )
require . NotNil ( t , ev . fields )
require . Equal ( t , "1.2.3.4" , ev . fields [ "visitor_ip" ] )
// Not logged, but contexters called, because overrides exist
SetLevel ( DebugLevel )
SetLevelOverride ( "tag" , "overridetag" , TraceLevel )
v . contextCalled = false
ev = Tag ( "sometag" ) . Field ( "field" , "value" ) . With ( v ) . Time ( time . Unix ( 123 , 0 ) . UTC ( ) )
ev . Trace ( "some debug message" )
require . True ( t , v . contextCalled ) // If there are overrides, we must call the context to determine the filter fields
require . Equal ( t , "" , ev . Timestamp )
require . Equal ( t , Level ( 0 ) , ev . Level )
require . Equal ( t , "" , ev . Message )
require . Equal ( t , 4 , len ( ev . fields ) )
require . Equal ( t , "value" , ev . fields [ "field" ] )
require . Equal ( t , "sometag" , ev . fields [ "tag" ] )
// Logged because of override tag, and contexters called
v . contextCalled = false
ev = Tag ( "overridetag" ) . Field ( "field" , "value" ) . With ( v ) . Time ( time . Unix ( 123 , 0 ) . UTC ( ) )
ev . Trace ( "some trace message" )
require . True ( t , v . contextCalled )
require . Equal ( t , "1970-01-01T00:02:03Z" , ev . Timestamp )
require . Equal ( t , TraceLevel , ev . Level )
require . Equal ( t , "some trace message" , ev . Message )
// Logged because of field override, and contexters called
ResetLevelOverrides ( )
SetLevelOverride ( "visitor_ip" , "1.2.3.4" , TraceLevel )
v . contextCalled = false
ev = With ( v ) . Time ( time . Unix ( 124 , 0 ) . UTC ( ) )
ev . Trace ( "some trace message with override" )
require . True ( t , v . contextCalled )
require . Equal ( t , "1970-01-01T00:02:04Z" , ev . Timestamp )
require . Equal ( t , TraceLevel , ev . Level )
require . Equal ( t , "some trace message with override" , ev . Message )
expected := ` { "time" : "1970-01-01T00:18:31Z" , "level" : "INFO" , "message" : "some message" , "user_id" : "u_abc" , "visitor_ip" : "1.2.3.4" }
{ "time" : "1970-01-01T00:02:03Z" , "level" : "TRACE" , "message" : "some trace message" , "field" : "value" , "tag" : "overridetag" , "user_id" : "u_abc" , "visitor_ip" : "1.2.3.4" }
{ "time" : "1970-01-01T00:02:04Z" , "level" : "TRACE" , "message" : "some trace message with override" , "user_id" : "u_abc" , "visitor_ip" : "1.2.3.4" }
`
require . Equal ( t , expected , out . String ( ) )
}
2023-02-09 23:24:12 +03:00
func TestLog_Timing ( t * testing . T ) {
t . Cleanup ( resetState )
var out bytes . Buffer
SetOutput ( & out )
SetFormat ( JSONFormat )
Timing ( func ( ) { time . Sleep ( 300 * time . Millisecond ) } ) .
Time ( time . Unix ( 12 , 0 ) . UTC ( ) ) .
Info ( "A thing that takes a while" )
var ev struct {
TimeTakenMs int64 ` json:"time_taken_ms" `
}
require . Nil ( t , json . Unmarshal ( out . Bytes ( ) , & ev ) )
require . True ( t , ev . TimeTakenMs >= 300 )
require . Contains ( t , out . String ( ) , ` { "time":"1970-01-01T00:00:12Z","level":"INFO","message":"A thing that takes a while","time_taken_ms": ` )
}
2023-02-10 04:49:45 +03:00
func TestLog_LevelOverrideAny ( t * testing . T ) {
t . Cleanup ( resetState )
var out bytes . Buffer
SetOutput ( & out )
SetFormat ( JSONFormat )
SetLevelOverride ( "this_one" , "" , DebugLevel )
SetLevelOverride ( "time_taken_ms" , "" , TraceLevel )
Time ( time . Unix ( 11 , 0 ) . UTC ( ) ) . Field ( "this_one" , "11" ) . Debug ( "this is logged" )
Time ( time . Unix ( 12 , 0 ) . UTC ( ) ) . Field ( "not_this" , "11" ) . Debug ( "this is not logged" )
Time ( time . Unix ( 13 , 0 ) . UTC ( ) ) . Field ( "this_too" , "11" ) . Info ( "this is also logged" )
Time ( time . Unix ( 14 , 0 ) . UTC ( ) ) . Field ( "time_taken_ms" , 0 ) . Info ( "this is also logged" )
expected := ` { "time" : "1970-01-01T00:00:11Z" , "level" : "DEBUG" , "message" : "this is logged" , "this_one" : "11" }
{ "time" : "1970-01-01T00:00:13Z" , "level" : "INFO" , "message" : "this is also logged" , "this_too" : "11" }
{ "time" : "1970-01-01T00:00:14Z" , "level" : "INFO" , "message" : "this is also logged" , "time_taken_ms" : 0 }
`
require . Equal ( t , expected , out . String ( ) )
2023-02-18 04:52:48 +03:00
require . False ( t , IsFile ( ) )
require . Equal ( t , "" , File ( ) )
}
2023-02-27 19:03:21 +03:00
func TestLog_LevelOverride_ManyOnSameField ( t * testing . T ) {
t . Cleanup ( resetState )
var out bytes . Buffer
SetOutput ( & out )
SetFormat ( JSONFormat )
SetLevelOverride ( "tag" , "manager" , DebugLevel )
SetLevelOverride ( "tag" , "publish" , DebugLevel )
Time ( time . Unix ( 11 , 0 ) . UTC ( ) ) . Field ( "tag" , "manager" ) . Debug ( "this is logged" )
Time ( time . Unix ( 12 , 0 ) . UTC ( ) ) . Field ( "tag" , "no-match" ) . Debug ( "this is not logged" )
Time ( time . Unix ( 13 , 0 ) . UTC ( ) ) . Field ( "tag" , "publish" ) . Info ( "this is also logged" )
expected := ` { "time" : "1970-01-01T00:00:11Z" , "level" : "DEBUG" , "message" : "this is logged" , "tag" : "manager" }
{ "time" : "1970-01-01T00:00:13Z" , "level" : "INFO" , "message" : "this is also logged" , "tag" : "publish" }
`
require . Equal ( t , expected , out . String ( ) )
require . False ( t , IsFile ( ) )
require . Equal ( t , "" , File ( ) )
}
2023-05-17 17:39:15 +03:00
func TestLog_FieldIf ( t * testing . T ) {
t . Cleanup ( resetState )
var out bytes . Buffer
SetOutput ( & out )
SetLevel ( DebugLevel )
SetFormat ( JSONFormat )
Time ( time . Unix ( 11 , 0 ) . UTC ( ) ) .
FieldIf ( "trace_field" , "manager" , TraceLevel ) . // This is not logged
Field ( "tag" , "manager" ) .
Debug ( "trace_field is not logged" )
SetLevel ( TraceLevel )
Time ( time . Unix ( 12 , 0 ) . UTC ( ) ) .
FieldIf ( "trace_field" , "manager" , TraceLevel ) . // Now it is logged
Field ( "tag" , "manager" ) .
Debug ( "trace_field is logged" )
expected := ` { "time" : "1970-01-01T00:00:11Z" , "level" : "DEBUG" , "message" : "trace_field is not logged" , "tag" : "manager" }
{ "time" : "1970-01-01T00:00:12Z" , "level" : "DEBUG" , "message" : "trace_field is logged" , "tag" : "manager" , "trace_field" : "manager" }
`
require . Equal ( t , expected , out . String ( ) )
}
2023-02-18 04:52:48 +03:00
func TestLog_UsingStdLogger_JSON ( t * testing . T ) {
t . Cleanup ( resetState )
var out bytes . Buffer
SetOutput ( & out )
SetFormat ( JSONFormat )
log . Println ( "Some other library is using the standard Go logger" )
require . Contains ( t , out . String ( ) , ` ,"level":"INFO","message":"Some other library is using the standard Go logger","tag":"stdlog"} ` + "\n" )
}
func TestLog_UsingStdLogger_Text ( t * testing . T ) {
t . Cleanup ( resetState )
var out bytes . Buffer
SetOutput ( & out )
log . Println ( "Some other library is using the standard Go logger" )
require . Contains ( t , out . String ( ) , ` Some other library is using the standard Go logger ` + "\n" )
require . NotContains ( t , out . String ( ) , ` { ` )
}
func TestLog_File ( t * testing . T ) {
t . Cleanup ( resetState )
logfile := filepath . Join ( t . TempDir ( ) , "ntfy.log" )
f , err := os . OpenFile ( logfile , os . O_CREATE | os . O_WRONLY , 0600 )
require . Nil ( t , err )
SetOutput ( f )
SetFormat ( JSONFormat )
require . True ( t , IsFile ( ) )
require . Equal ( t , logfile , File ( ) )
Time ( time . Unix ( 11 , 0 ) . UTC ( ) ) . Field ( "this_one" , "11" ) . Info ( "this is logged" )
require . Nil ( t , f . Close ( ) )
f , err = os . Open ( logfile )
require . Nil ( t , err )
contents , err := io . ReadAll ( f )
require . Nil ( t , err )
require . Equal ( t , ` { "time":"1970-01-01T00:00:11Z","level":"INFO","message":"this is logged","this_one":"11"} ` + "\n" , string ( contents ) )
2023-02-10 04:49:45 +03:00
}
2023-02-07 00:01:32 +03:00
type fakeError struct {
Code int
Message string
}
func ( e fakeError ) Error ( ) string {
return e . Message
}
2023-02-08 06:10:51 +03:00
func ( e fakeError ) Context ( ) Context {
return Context {
2023-02-07 00:01:32 +03:00
"error" : e . Message ,
"error_code" : e . Code ,
}
}
2023-02-06 07:34:27 +03:00
type fakeVisitor struct {
2023-02-08 06:10:51 +03:00
UserID string
IP string
contextCalled bool
2023-02-04 06:21:50 +03:00
}
2023-02-08 06:10:51 +03:00
func ( v * fakeVisitor ) Context ( ) Context {
v . contextCalled = true
return Context {
2023-02-06 07:34:27 +03:00
"user_id" : v . UserID ,
"visitor_ip" : v . IP ,
2023-02-04 06:21:50 +03:00
}
}
2023-02-06 07:34:27 +03:00
func resetState ( ) {
2023-02-08 06:10:51 +03:00
SetLevel ( DefaultLevel )
SetFormat ( DefaultFormat )
SetOutput ( DefaultOutput )
ResetLevelOverrides ( )
2023-02-04 06:21:50 +03:00
}