REPOSITORY / ScuroNeko/Laniakea

Pull Requests

PULL REQUESTS REPOSITORY

v1.0.0 #9

Merged
ScuroNeko merged 101 commits from dev into main 2026-05-20 13:43:34 +03:00
23 changed files with 245 additions and 185 deletions
Showing only changes of commit a34734366d - Show all commits
+3
View File
@@ -3,11 +3,14 @@
## v1.0.0-rc.16
### Changed
- Updated `slog` to `v2`.
- Bot loggers now apply the configured token replacer consistently across the main bot logger, request logger, internal API and uploader loggers, webhook logger, and auto-managed plugin loggers, so bot tokens stay masked in both stdout and file-backed logs.
- JSON `BotOpts` files now write `version`, reject newer unsupported config versions, keep older unversioned files loadable, and preserve the loaded file version in `BotOpts.FileConfigVersion`.
- Active scenes now support scene-local callback payload handlers through `Scene.OnPayload(...)`, including observer lifecycle events for scene payload execution.
### Tests
- Added regression coverage proving polling startup preserves an enabled request logger.
- Updated file logger regression coverage for the current `sneklog` text prefix format.
- Added regression coverage proving token masking still applies after `initLoggers(...)` switches loggers to file-backed writers and that auto-managed plugin loggers inherit token masking as well.
- Added regression coverage for JSON config version handling and scene-local payload routing, including observer lifecycle events and callback fallthrough behavior.
+62 -19
View File
@@ -10,7 +10,7 @@ import (
"git.scuroneko.dev/scuroneko/extypes"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/laniakea/utils"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
// AppData is the generic shared application data type injected into bots,
@@ -38,11 +38,11 @@ type AppData any
// Use Bot[NoData] to indicate no shared dependency injection is required.
type NoData struct{ AppData }
// AppDataLogger builds a slog.LoggerWriter from injected application data.
// AppDataLogger builds a sneklog.LoggerWriter from injected application data.
//
// Use it when shared application data exposes a log sink or adapter that should
// receive framework logs.
type AppDataLogger[T AppData] func(data T) slog.LoggerWriter
type AppDataLogger[T AppData] func(data T) sneklog.LoggerWriter
// BotPayloadType defines the serialization format for callback data payloads.
type BotPayloadType string
@@ -90,10 +90,11 @@ type Bot[T AppData] struct {
strictPayloadType bool
maxWorkers int
logger *slog.Logger // Main bot logger (JSON stdout + optional file)
RequestLogger *slog.Logger // Optional request-level API logging
webHookLogger *slog.Logger // Webhook logger. Available only after Bot.RunWebHookWithContext.
extraLoggers extypes.Slice[*slog.Logger] // API, Uploader, and custom loggers
logger *sneklog.Logger // Main bot logger (JSON stdout + optional file)
requestLogger *sneklog.Logger // Optional request-level API logging
useReqLogger bool
webHookLogger *sneklog.Logger // Webhook logger. Available only after Bot.RunWebHookWithContext.
extraLoggers extypes.Slice[*sneklog.Logger] // API, Uploader, and custom loggers
plugins []Plugin[T] // Command/event handlers
middlewares []Middleware[T] // Pre-processing filters (sorted by order)
@@ -187,12 +188,14 @@ func NewBot[T any](opts *BotOpts) (*Bot[T], error) {
debug: opts.Debug,
prefixes: prefixes,
token: opts.Token,
plugins: make([]Plugin[T], 0),
updateTypes: append([]tgapi.UpdateType{}, opts.UpdateTypes...),
runners: make([]Runner[T], 0),
extraLoggers: make([]*slog.Logger, 0),
l10n: &L10n{},
draftProvider: NewRandomDraftProvider(api),
useReqLogger: opts.UseRequestLogger,
plugins: make([]Plugin[T], 0),
updateTypes: append([]tgapi.UpdateType{}, opts.UpdateTypes...),
runners: make([]Runner[T], 0),
extraLoggers: make([]*sneklog.Logger, 0),
l10n: &L10n{},
draftProvider: NewRandomDraftProvider(api),
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -235,6 +238,24 @@ func NewBot[T any](opts *BotOpts) (*Bot[T], error) {
return bot, nil
}
// SetLogger replaces the main bot logger.
func (b *Bot[T]) SetLogger(l *sneklog.Logger) *Bot[T] {
b.logger = l
return b
}
// SetRequestLogger replaces the request-level logger.
func (b *Bot[T]) SetRequestLogger(l *sneklog.Logger) *Bot[T] {
b.requestLogger = l
return b
}
// SetWebHookLogger replaces the webhook logger.
func (b *Bot[T]) SetWebHookLogger(l *sneklog.Logger) *Bot[T] {
b.webHookLogger = l
return b
}
// Close gracefully shuts down bot-owned resources.
//
// Close shuts down, in order:
@@ -283,8 +304,8 @@ func (bot *Bot[T]) Close() error {
logCloseErr(err)
}
}
if bot.RequestLogger != nil {
if err := bot.RequestLogger.Close(); err != nil {
if bot.requestLogger != nil {
if err := bot.requestLogger.Close(); err != nil {
logCloseErr(err)
}
}
@@ -322,14 +343,20 @@ func (bot *Bot[T]) SetUpdateOffset(offset int) {
}
// GetLogger returns the main bot logger.
func (bot *Bot[T]) GetLogger() *slog.Logger { return bot.logger }
func (bot *Bot[T]) GetLogger() *sneklog.Logger { return bot.logger }
// GetRequestLogger returns the request-level logger, if configured.
func (bot *Bot[T]) GetRequestLogger() *sneklog.Logger { return bot.requestLogger }
// GetWebHookLogger returns the webhook logger, if configured.
func (bot *Bot[T]) GetWebHookLogger() *sneklog.Logger { return bot.webHookLogger }
// GetLoggerLevel returns the effective log level derived from the bot's debug
// flag.
func (bot *Bot[T]) GetLoggerLevel() slog.LogLevel {
level := slog.FATAL
func (bot *Bot[T]) GetLoggerLevel() sneklog.LogLevel {
level := sneklog.FATAL
if bot.debug {
level = slog.DEBUG
level = sneklog.DEBUG
}
return level
}
@@ -373,6 +400,22 @@ func (bot *Bot[T]) RunWithContext(ctx context.Context) error {
return err
}
defer bot.finishRun()
if !bot.useReqLogger && bot.requestLogger != nil {
bot.logger.Warnln("Opts#UseRequestLogger is false, but Bot#requestLogger present. Remove Bot#SetRequestLogger or set Opts#UseRequestLogger to true!")
err := bot.requestLogger.Close()
if err != nil {
bot.logger.Errorln(err)
}
bot.requestLogger = nil
}
if bot.webHookLogger != nil {
bot.logger.Warnln("Bot#webHookLogger present. You shouldn't set this, if ran in Long Polling mode!")
err := bot.webHookLogger.Close()
if err != nil {
bot.logger.Errorln(err)
}
bot.webHookLogger = nil
}
bot.ExecRunners(ctx)
+5 -5
View File
@@ -5,7 +5,7 @@ import (
"slices"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
// AddPrefixes adds one or more command prefixes (e.g., "/", "!").
@@ -187,14 +187,14 @@ func (bot *Bot[T]) SetErrorTemplate(s string) *Bot[T] {
// SetDebug enables or disables debug logging.
func (bot *Bot[T]) SetDebug(debug bool) *Bot[T] {
bot.debug = debug
level := slog.FATAL
level := sneklog.FATAL
if debug {
level = slog.DEBUG
level = sneklog.DEBUG
}
bot.logger.Level(level)
if bot.RequestLogger != nil {
bot.RequestLogger.Level(level)
if bot.requestLogger != nil {
bot.requestLogger.Level(level)
}
for _, p := range bot.plugins {
if p.logger == nil {
+4 -4
View File
@@ -129,7 +129,7 @@ func (bot *Bot[T]) AddRunner(runner Runner[T]) *Bot[T] {
//
// Example:
//
// bot.AddAppDataLoggerWriter(func(data *MyAppData) slog.LoggerWriter {
// bot.AddAppDataLoggerWriter(func(data *MyAppData) sneklog.LoggerWriter {
// return data.QueryLogger()
// })
func (bot *Bot[T]) AddAppDataLoggerWriter(writer AppDataLogger[T]) *Bot[T] {
@@ -143,8 +143,8 @@ func (bot *Bot[T]) AddAppDataLoggerWriter(writer AppDataLogger[T]) *Bot[T] {
}
w := writer(bot.appData)
bot.logger.AddWriter(w)
if bot.RequestLogger != nil {
bot.RequestLogger.AddWriter(w)
if bot.requestLogger != nil {
bot.requestLogger.AddWriter(w)
}
for _, l := range bot.managedExtraLoggers() {
l.AddWriter(w)
@@ -154,7 +154,7 @@ func (bot *Bot[T]) AddAppDataLoggerWriter(writer AppDataLogger[T]) *Bot[T] {
p.logger.AddWriter(w)
}
}
bot.addTokenReplacer(bot.logger, bot.RequestLogger)
bot.addTokenReplacer(bot.logger, bot.requestLogger)
bot.addTokenReplacer(bot.managedExtraLoggers()...)
return bot
}
+48 -22
View File
@@ -13,7 +13,7 @@ import (
"time"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
type pollingRoundTripFunc func(*http.Request) (*http.Response, error)
@@ -59,7 +59,7 @@ func TestGetUpdateTypesReturnsCopy(t *testing.T) {
}
func TestAddPluginsSnapshotsConfiguration(t *testing.T) {
bot := &Bot[NoData]{logger: slog.CreateLogger()}
bot := &Bot[NoData]{logger: sneklog.CreateLogger()}
plugin := NewPlugin[NoData]("demo")
cmd := plugin.NewCommand(func(ctx *MsgContext, db NoData) error { return nil }, "start")
@@ -100,7 +100,7 @@ func TestBotPayloadTypeConfiguration(t *testing.T) {
}
func TestAddPluginsSkipsNilPlugin(t *testing.T) {
bot := &Bot[NoData]{logger: slog.CreateLogger()}
bot := &Bot[NoData]{logger: sneklog.CreateLogger()}
plugin := NewPlugin[NoData]("demo")
bot.AddPlugins(nil, plugin)
@@ -126,10 +126,10 @@ func TestInitLoggersFallsBackToStdoutLoggerOnFileError(t *testing.T) {
if bot.logger == nil {
t.Fatal("expected main logger fallback")
}
if bot.RequestLogger == nil {
if bot.requestLogger == nil {
t.Fatal("expected request logger fallback")
}
if err := bot.RequestLogger.Close(); err != nil {
if err := bot.requestLogger.Close(); err != nil {
t.Fatalf("failed to close request logger: %v", err)
}
if err := bot.logger.Close(); err != nil {
@@ -177,11 +177,11 @@ func TestInitLoggersAppliesTokenReplacerToFileLoggers(t *testing.T) {
bot.uploader.GetLogger().AddWriter(bot.uploader.GetLogger().CreateTextWriter(uploaderFile))
bot.logger.Infoln("main secret-token")
bot.RequestLogger.Infoln("request secret-token")
bot.requestLogger.Infoln("request secret-token")
bot.api.GetLogger().Infoln("api secret-token")
bot.uploader.GetLogger().Infoln("uploader secret-token")
if err := bot.RequestLogger.Close(); err != nil {
if err := bot.requestLogger.Close(); err != nil {
t.Fatalf("failed to close request logger: %v", err)
}
if err := bot.logger.Close(); err != nil {
@@ -226,7 +226,7 @@ func TestInitLoggersAppliesTokenReplacerToFileLoggers(t *testing.T) {
func TestAddPluginsAppliesTokenReplacerToPluginLogger(t *testing.T) {
bot := &Bot[NoData]{
token: "secret-token",
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
}
defer func() { _ = bot.logger.Close() }()
@@ -276,10 +276,10 @@ func TestNextPollRetryDelay(t *testing.T) {
}
func TestAddDatabaseLoggerWriterSkipsWhenAppDataIsUnset(t *testing.T) {
bot := &Bot[NoData]{logger: slog.CreateLogger()}
bot := &Bot[NoData]{logger: sneklog.CreateLogger()}
called := false
bot.AddAppDataLoggerWriter(func(db NoData) slog.LoggerWriter {
bot.AddAppDataLoggerWriter(func(db NoData) sneklog.LoggerWriter {
called = true
return nil
})
@@ -292,12 +292,12 @@ func TestAddDatabaseLoggerWriterSkipsWhenAppDataIsUnset(t *testing.T) {
func TestAddDatabaseLoggerWriterSkipsWhenAppDataIsNil(t *testing.T) {
type testDB struct{}
bot := &Bot[*testDB]{logger: slog.CreateLogger()}
bot := &Bot[*testDB]{logger: sneklog.CreateLogger()}
var db *testDB
bot.SetAppData(db)
called := false
bot.AddAppDataLoggerWriter(func(db *testDB) slog.LoggerWriter {
bot.AddAppDataLoggerWriter(func(db *testDB) sneklog.LoggerWriter {
called = true
return nil
})
@@ -336,13 +336,13 @@ func TestShouldWarnOnValueAppData(t *testing.T) {
func TestSetAppDataMarksValueWarningOnce(t *testing.T) {
type testDB struct{}
bot := &Bot[testDB]{logger: slog.CreateLogger()}
bot := &Bot[testDB]{logger: sneklog.CreateLogger()}
bot.SetAppData(testDB{})
if !bot.warnedValueData {
t.Fatal("expected value-typed app data to mark warning state")
}
ptrBot := &Bot[*testDB]{logger: slog.CreateLogger()}
ptrBot := &Bot[*testDB]{logger: sneklog.CreateLogger()}
ptrBot.SetAppData(&testDB{})
if ptrBot.warnedValueData {
t.Fatal("did not expect pointer-typed app data to mark warning state")
@@ -350,7 +350,7 @@ func TestSetAppDataMarksValueWarningOnce(t *testing.T) {
}
func TestSetObserverAndGetObserver(t *testing.T) {
bot := &Bot[NoData]{logger: slog.CreateLogger()}
bot := &Bot[NoData]{logger: sneklog.CreateLogger()}
observer := testObserver{}
if got := bot.GetObserver(); got != nil {
@@ -364,7 +364,7 @@ func TestSetObserverAndGetObserver(t *testing.T) {
}
func TestSetObserverNilClearsObserver(t *testing.T) {
bot := &Bot[NoData]{logger: slog.CreateLogger()}
bot := &Bot[NoData]{logger: sneklog.CreateLogger()}
bot.SetObserver(testObserver{})
if bot.GetObserver() == nil {
@@ -382,7 +382,7 @@ func TestRunWithContextRejectsSecondRun(t *testing.T) {
cancel()
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
plugins: []Plugin[NoData]{{name: "demo"}},
updateQueue: make(chan *tgapi.Update, 1),
@@ -397,6 +397,32 @@ func TestRunWithContextRejectsSecondRun(t *testing.T) {
}
}
func TestRunWithContextKeepsEnabledRequestLogger(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
requestLogger := sneklog.CreateLogger()
bot := &Bot[NoData]{
logger: sneklog.CreateLogger(),
requestLogger: requestLogger,
useReqLogger: true,
prefixes: []string{"/"},
plugins: []Plugin[NoData]{{name: "demo"}},
updateQueue: make(chan *tgapi.Update, 1),
maxWorkers: 1,
}
t.Cleanup(func() {
_ = bot.Close()
})
if err := bot.RunWithContext(ctx); err != nil {
t.Fatalf("RunWithContext returned error: %v", err)
}
if got := bot.GetRequestLogger(); got != requestLogger {
t.Fatalf("expected enabled request logger to be preserved, got %#v", got)
}
}
func TestCloseDoesNotDeleteWebhook(t *testing.T) {
requests := 0
client := &http.Client{
@@ -418,8 +444,8 @@ func TestCloseDoesNotDeleteWebhook(t *testing.T) {
uploader := tgapi.NewUploader(api)
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
webHookLogger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
webHookLogger: sneklog.CreateLogger(),
api: api,
uploader: uploader,
}
@@ -457,7 +483,7 @@ func TestRunWithContextEmitsPollingRetryAndErrorEvents(t *testing.T) {
}()
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
api: api,
prefixes: []string{"/"},
plugins: []Plugin[NoData]{{name: "demo"}},
@@ -489,7 +515,7 @@ func TestBotConfigurationFreezesAfterRunStarts(t *testing.T) {
makeBot := func() *Bot[*testDB] {
return &Bot[*testDB]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
updateTypes: []tgapi.UpdateType{tgapi.UpdateTypeMessage},
payloadType: BotPayloadBase64,
@@ -681,7 +707,7 @@ func TestBotConfigurationFreezesAfterRunStarts(t *testing.T) {
func TestAddPluginsAndRuntimeRegistrationsNoOpAfterRunStarts(t *testing.T) {
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
middlewares: []Middleware[NoData]{NewMiddleware("base", func(ctx *MsgContext, db NoData) bool { return true })},
runners: []Runner[NoData]{NewRunner("base", func(bot *Bot[NoData]) error { return nil })},
+22 -19
View File
@@ -12,11 +12,12 @@ import (
"git.scuroneko.dev/scuroneko/extypes"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/laniakea/utils"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
"github.com/alitto/pond/v2"
)
func (bot *Bot[T]) addTokenReplacer(loggers ...*slog.Logger) {
func (bot *Bot[T]) addTokenReplacer(loggers ...*sneklog.Logger) {
if bot.token == "" {
return
}
@@ -28,7 +29,7 @@ func (bot *Bot[T]) addTokenReplacer(loggers ...*slog.Logger) {
}
}
func appendUniqueLogger(loggers []*slog.Logger, logger *slog.Logger) []*slog.Logger {
func appendUniqueLogger(loggers []*sneklog.Logger, logger *sneklog.Logger) []*sneklog.Logger {
if logger == nil {
return loggers
}
@@ -38,8 +39,8 @@ func appendUniqueLogger(loggers []*slog.Logger, logger *slog.Logger) []*slog.Log
return append(loggers, logger)
}
func (bot *Bot[T]) managedExtraLoggers() []*slog.Logger {
loggers := append([]*slog.Logger(nil), bot.extraLoggers...)
func (bot *Bot[T]) managedExtraLoggers() []*sneklog.Logger {
loggers := append([]*sneklog.Logger(nil), bot.extraLoggers...)
if bot.api != nil {
loggers = appendUniqueLogger(loggers, bot.api.GetLogger())
}
@@ -70,36 +71,38 @@ func (bot *Bot[T]) startUpdateWorkers(ctx context.Context) {
}
func (bot *Bot[T]) initLoggers(opts *BotOpts) {
level := slog.FATAL
level := sneklog.FATAL
if opts.Debug {
level = slog.DEBUG
level = sneklog.DEBUG
}
bot.logger = utils.CreateLogger("BOT", level)
if opts.WriteToFile {
path := fmt.Sprintf("%s/main.log", strings.TrimRight(opts.LoggerBasePath, "/"))
logger, err := utils.CreateFileLogger("BOT", level, path)
if err != nil {
bot.logger.Errorln(err)
} else {
bot.logger = logger
if bot.logger == nil {
bot.logger = utils.CreateLogger("BOT", level)
if opts.WriteToFile {
path := fmt.Sprintf("%s/main.log", strings.TrimRight(opts.LoggerBasePath, "/"))
logger, err := utils.CreateFileLogger("BOT", level, path)
if err != nil {
bot.logger.Errorln(err)
} else {
bot.logger = logger
}
}
}
if opts.UseRequestLogger {
bot.RequestLogger = utils.CreateLogger("REQUESTS", level)
if opts.UseRequestLogger && bot.requestLogger == nil {
bot.requestLogger = utils.CreateLogger("REQUESTS", level)
if opts.WriteToFile {
path := fmt.Sprintf("%s/requests.log", strings.TrimRight(opts.LoggerBasePath, "/"))
logger, err := utils.CreateFileLogger("REQUESTS", level, path)
if err != nil {
bot.logger.Errorln(err)
} else {
bot.RequestLogger = logger
bot.requestLogger = logger
}
}
}
bot.addTokenReplacer(bot.logger, bot.RequestLogger)
bot.addTokenReplacer(bot.logger, bot.requestLogger)
bot.addTokenReplacer(bot.managedExtraLoggers()...)
}
+6 -6
View File
@@ -11,7 +11,7 @@ import (
"testing"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
func TestEnqueueUpdateCopiesValue(t *testing.T) {
@@ -35,7 +35,7 @@ func TestEnqueueUpdateCopiesValue(t *testing.T) {
func TestUpdateHandlerEnqueuesUpdate(t *testing.T) {
bot := &Bot[NoData]{
updateQueue: make(chan *tgapi.Update, 1),
webHookLogger: slog.CreateLogger(),
webHookLogger: sneklog.CreateLogger(),
}
t.Cleanup(func() {
_ = bot.webHookLogger.Close()
@@ -66,7 +66,7 @@ func TestUpdateHandlerEnqueuesUpdate(t *testing.T) {
func TestRunWebhookRuntimeRejectsSecondRun(t *testing.T) {
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
updateQueue: make(chan *tgapi.Update, 1),
maxWorkers: 1,
}
@@ -86,7 +86,7 @@ func TestRunWebhookRuntimeExecutesRunners(t *testing.T) {
var calls atomic.Int32
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
updateQueue: make(chan *tgapi.Update, 1),
maxWorkers: 1,
runners: []Runner[NoData]{
@@ -185,7 +185,7 @@ func TestValidateWebhookTLSFiles(t *testing.T) {
func TestUpdateHandlerRejectsOversizedBody(t *testing.T) {
bot := &Bot[NoData]{
updateQueue: make(chan *tgapi.Update, 1),
webHookLogger: slog.CreateLogger(),
webHookLogger: sneklog.CreateLogger(),
}
t.Cleanup(func() {
_ = bot.webHookLogger.Close()
@@ -222,7 +222,7 @@ func TestStatusHandlerRequiresMatchingSecret(t *testing.T) {
bot := &Bot[NoData]{
api: api,
webHookLogger: slog.CreateLogger(),
webHookLogger: sneklog.CreateLogger(),
}
t.Cleanup(func() {
_ = bot.webHookLogger.Close()
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"testing"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
type roundTripFunc func(*http.Request) (*http.Response, error)
@@ -51,7 +51,7 @@ func TestAutoGenerateCommandsChecksLimitBeforeDelete(t *testing.T) {
bot := &Bot[NoData]{
api: api,
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
plugins: []Plugin[NoData]{*plugin},
}
+2 -2
View File
@@ -6,7 +6,7 @@ import (
"testing"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
func TestDraftFlushRequiresChatID(t *testing.T) {
@@ -24,7 +24,7 @@ func TestMsgContextNewDraftWorksWithoutLimiter(t *testing.T) {
Msg: &tgapi.Message{
Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate},
},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
draftProvider: NewRandomDraftProvider(&tgapi.API{}),
}
+2 -9
View File
@@ -6,14 +6,7 @@ retract v1.0.0-rc.5
require (
git.scuroneko.dev/scuroneko/extypes v1.2.3
git.scuroneko.dev/scuroneko/slog v1.2.0
github.com/alitto/pond/v2 v2.7.0
git.scuroneko.dev/scuroneko/sneklog/v2 v2.0.1
github.com/alitto/pond/v2 v2.7.1
golang.org/x/time v0.15.0
)
require (
github.com/fatih/color v1.19.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
golang.org/x/sys v0.43.0 // indirect
)
+4 -12
View File
@@ -1,16 +1,8 @@
git.scuroneko.dev/scuroneko/extypes v1.2.3 h1:n7QsfTZEn9fJNZLXGH/LkNq4cADaRk+LTu6LNMv9y6s=
git.scuroneko.dev/scuroneko/extypes v1.2.3/go.mod h1:MhYpXC6sloLOpoM2guf64eSOrz+ET/QJZ8toobc3Ors=
git.scuroneko.dev/scuroneko/slog v1.2.0 h1:xbwzrMcmN0NG/zTgEn508mn2JVnfZN5z/Zsi3PREfDM=
git.scuroneko.dev/scuroneko/slog v1.2.0/go.mod h1:r+oz9NzvvdtWd9/PjeS+n5vQoNHL38BdcdLoBtJPvFU=
github.com/alitto/pond/v2 v2.7.0 h1:c76L+yN916m/DRXjGCeUBHHu92uWnh/g1bwVk4zyyXg=
github.com/alitto/pond/v2 v2.7.0/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
git.scuroneko.dev/scuroneko/sneklog/v2 v2.0.1 h1:nDGLsvxbYoSxSk9wrGCmb+tn5jPM+KYlBX+3ciyC2Yo=
git.scuroneko.dev/scuroneko/sneklog/v2 v2.0.1/go.mod h1:q8XnLXzLdGjW0Jtcbh9/+G9WmfD68rsPQvLXEPxvum4=
github.com/alitto/pond/v2 v2.7.1 h1:QxMbcfjcVTa0pyxX5Ib1226mM8u8D7gKUVkCUU4DYIw=
github.com/alitto/pond/v2 v2.7.1/go.mod h1:xkjYEgQ05RSpWdfSd1nM3OVv7TBhLdy7rMp3+2Nq+yE=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+19 -19
View File
@@ -6,7 +6,7 @@ import (
"testing"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
type recordingObserver struct {
@@ -55,7 +55,7 @@ func TestCheckPrefixesSkipsEmptyPrefixes(t *testing.T) {
}
func TestBotMiddlewareReceivesLogger(t *testing.T) {
logger := slog.CreateLogger()
logger := sneklog.CreateLogger()
called := false
bot := &Bot[NoData]{
@@ -468,7 +468,7 @@ func TestHandleUpdateHandlersPopulateFromContext(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -514,7 +514,7 @@ func TestHandleUpdateHandlersReceiveIsolatedContexts(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
plugins: []Plugin[NoData]{
clonePlugin(first),
clonePlugin(second),
@@ -543,7 +543,7 @@ func TestHandleUpdateObserverEmitsUpdateErrors(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
plugins: []Plugin[NoData]{clonePlugin(plugin)},
observer: observer,
}
@@ -607,7 +607,7 @@ func TestHandleMessageFallbackRunsAfterCommandMiss(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
observer: observer,
}
@@ -658,7 +658,7 @@ func TestHandleMessageFallbackRunsForPlainText(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
}
bot.AddPlugins(plugin)
@@ -691,7 +691,7 @@ func TestHandleMessageFallbackRespectsMiddleware(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
}
bot.AddPlugins(plugin)
@@ -726,7 +726,7 @@ func TestHandleMessageFallbackDoesNotRunWhenCommandMatches(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
}
bot.AddPlugins(plugin)
@@ -771,7 +771,7 @@ func TestHandleChannelPostCommandWithSenderChat(t *testing.T) {
}, "ping")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -808,7 +808,7 @@ func TestCommandHandlerBindArgsEndToEnd(t *testing.T) {
)
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -845,7 +845,7 @@ func TestPayloadHandlerBindArgsEndToEnd(t *testing.T) {
)
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -898,7 +898,7 @@ func TestHandleEditedMessageStaysOutOfCommandFlow(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -940,7 +940,7 @@ func TestHandleEditedChannelPostStaysOutOfCommandFlow(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -993,7 +993,7 @@ func TestHandleCallbackPopulatesMessageTargets(t *testing.T) {
}, "approve")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -1052,7 +1052,7 @@ func TestHandleCallbackPopulatesInlineTargets(t *testing.T) {
}, "inline.approve")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
plugins: []Plugin[NoData]{clonePlugin(plugin)},
}
@@ -1086,7 +1086,7 @@ func TestHandleCallbackObserverEmitsPayloadEvents(t *testing.T) {
}, "approve")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
plugins: []Plugin[NoData]{clonePlugin(plugin)},
observer: observer,
@@ -1137,7 +1137,7 @@ func TestHandleCallbackObserverEmitsPayloadErrors(t *testing.T) {
}, "approve")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
plugins: []Plugin[NoData]{clonePlugin(plugin)},
observer: observer,
@@ -1182,7 +1182,7 @@ func TestHandleCallbackObserverEmitsPayloadErrors(t *testing.T) {
func TestHandleCallbackObserverEmitsDecodeErrors(t *testing.T) {
observer := &recordingObserver{}
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
observer: observer,
}
+2 -2
View File
@@ -53,13 +53,13 @@ func (bot *Bot[T]) Updates(ctx context.Context) ([]tgapi.Update, error) {
return nil, err
}
if bot.RequestLogger != nil {
if bot.requestLogger != nil {
for _, u := range updates {
j, err := json.Marshal(u)
if err != nil {
bot.GetLogger().Error(err)
}
bot.RequestLogger.Debugf("UPDATE %s\n", j)
bot.requestLogger.Debugf("UPDATE %s\n", j)
}
}
if len(updates) > 0 {
+2 -2
View File
@@ -11,7 +11,7 @@ import (
"time"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
// MsgContext holds the normalized per-update context passed to command, payload,
@@ -46,7 +46,7 @@ type MsgContext struct {
// Logger is the logger assigned by the matched plugin for the current handler call.
// It may fall back to the bot logger when the plugin has no dedicated logger.
Logger *slog.Logger
Logger *sneklog.Logger
// InlineMsgId is the inline message identifier for callback queries that target
// an inline message instead of a chat message.
+9 -9
View File
@@ -10,7 +10,7 @@ import (
"testing"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
func TestAnswerPhotoIncludesDirectMessagesTopicID(t *testing.T) {
@@ -50,7 +50,7 @@ func TestAnswerPhotoIncludesDirectMessagesTopicID(t *testing.T) {
Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate},
DirectMessageTopic: &tgapi.DirectMessageTopic{TopicID: 77},
},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
}
answer := ctx.AnswerPhoto("photo-id", "caption")
@@ -202,7 +202,7 @@ func TestErrorDefaultRemainsUserVisibleForMessageFlow(t *testing.T) {
ctx := &MsgContext{
Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate}},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
errorTemplate: "Error: %s",
}
@@ -238,7 +238,7 @@ func TestErrorInternalSkipsUserReplyForMessageFlow(t *testing.T) {
ctx := &MsgContext{
Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate}},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
errorTemplate: "Error: %s",
}
@@ -266,7 +266,7 @@ func TestErrorInternalSkipsCallbackAnswer(t *testing.T) {
ctx := &MsgContext{
Api: api,
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
errorTemplate: "%s",
CallbackQueryId: "cb-1",
}
@@ -309,7 +309,7 @@ func TestErrorUserVisibleAnswersCallback(t *testing.T) {
ctx := &MsgContext{
Api: api,
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
errorTemplate: "Oops: %s",
CallbackQueryId: "cb-1",
}
@@ -327,7 +327,7 @@ func TestErrorUserVisibleAnswersCallback(t *testing.T) {
func TestAnswerRejectsEmptyMessage(t *testing.T) {
ctx := &MsgContext{
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate}},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
}
if answer := ctx.Answer(""); answer != nil {
@@ -357,7 +357,7 @@ func TestAnswerRejectsLongMessageWithoutSendingRequest(t *testing.T) {
ctx := &MsgContext{
Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate}},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
}
if answer := ctx.Answer(strings.Repeat("a", maxMessageTextLen+1)); answer != nil {
@@ -441,7 +441,7 @@ func TestAnswerLongSplitsRequestsAndAttachesKeyboardToLastChunk(t *testing.T) {
ctx := &MsgContext{
Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate}},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
}
kb := NewInlineKeyboardJson(1).AddCallbackButton("A", "cmd")
text := strings.Repeat("a", maxMessageTextLen) + " " + strings.Repeat("b", 32)
+3 -3
View File
@@ -7,7 +7,7 @@ import (
"git.scuroneko.dev/scuroneko/extypes"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/laniakea/utils"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
// CommandValueType defines the expected type of command argument.
@@ -169,7 +169,7 @@ type Plugin[T AppData] struct {
scenes map[string]*Scene[T] // Optional scenes for multi-step interactions
middlewares extypes.Slice[Middleware[T]] // Shared middlewares for all commands/payloads
skipAutoCmd bool // If true, all commands in this plugin are excluded from auto-help
logger *slog.Logger
logger *sneklog.Logger
messageFallback CommandExecutor[T]
handlers map[tgapi.UpdateType]CommandExecutor[T]
@@ -293,7 +293,7 @@ func (p *Plugin[T]) SkipCommandAutoGen() *Plugin[T] {
//
// Call this before Bot.AddPlugins. If the plugin is already registered, changing
// the original *Plugin does not update the Bot's internal copy.
func (p *Plugin[T]) SetLogger(l *slog.Logger) *Plugin[T] {
func (p *Plugin[T]) SetLogger(l *sneklog.Logger) *Plugin[T] {
p.logger = l
return p
}
+13 -13
View File
@@ -10,7 +10,7 @@ import (
"testing"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
func TestRequirePolicyStopsExecutionOnDeniedPolicy(t *testing.T) {
@@ -49,7 +49,7 @@ func TestRequirePolicyStopsExecutionOnDeniedPolicy(t *testing.T) {
ctx := &MsgContext{
Api: api,
Msg: &tgapi.Message{Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate}},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
errorTemplate: "Error: %s",
}
@@ -73,7 +73,7 @@ func TestRequirePrivateChatAllowsPrivateChat(t *testing.T) {
Msg: &tgapi.Message{
Chat: &tgapi.Chat{ID: 42, Type: tgapi.ChatTypePrivate},
},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
}
if err := RequirePrivateChat[NoData]()(ctx, NoData{}); err != nil {
@@ -86,7 +86,7 @@ func TestRequirePrivateChatDeniesNonPrivateChat(t *testing.T) {
Msg: &tgapi.Message{
Chat: &tgapi.Chat{ID: -100, Type: tgapi.ChatTypeSupergroup},
},
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
}
err := RequirePrivateChat[NoData]()(ctx, NoData{})
@@ -140,7 +140,7 @@ func TestRequireChatAdminUsesNormalizedIDs(t *testing.T) {
Api: api,
ChatID: -2001,
FromID: 55,
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
}
if err := RequireChatAdmin[NoData]()(ctx, NoData{}); err != nil {
@@ -168,7 +168,7 @@ func TestAllPoliciesReturnsFirstError(t *testing.T) {
},
)
err := policy(&MsgContext{Logger: slog.CreateLogger()}, NoData{})
err := policy(&MsgContext{Logger: sneklog.CreateLogger()}, NoData{})
if !errors.Is(err, want) {
t.Fatalf("expected first policy error, got %v", err)
}
@@ -180,7 +180,7 @@ func TestAnyPolicyAllowsLaterSuccessAfterInternalError(t *testing.T) {
func(ctx *MsgContext, data NoData) error { return nil },
)
if err := policy(&MsgContext{Logger: slog.CreateLogger()}, NoData{}); err != nil {
if err := policy(&MsgContext{Logger: sneklog.CreateLogger()}, NoData{}); err != nil {
t.Fatalf("expected later success to allow access, got %v", err)
}
}
@@ -192,7 +192,7 @@ func TestAnyPolicyReturnsInternalErrorWhenNonePass(t *testing.T) {
func(ctx *MsgContext, data NoData) error { return internal },
)
err := policy(&MsgContext{Logger: slog.CreateLogger()}, NoData{})
err := policy(&MsgContext{Logger: sneklog.CreateLogger()}, NoData{})
if !errors.Is(err, internal) {
t.Fatalf("expected internal error, got %v", err)
}
@@ -205,7 +205,7 @@ func TestAnyPolicyReturnsFirstDenyWhenNoPolicyPasses(t *testing.T) {
func(ctx *MsgContext, data NoData) error { return AsUserError(errors.New("second deny")) },
)
err := policy(&MsgContext{Logger: slog.CreateLogger()}, NoData{})
err := policy(&MsgContext{Logger: sneklog.CreateLogger()}, NoData{})
if !errors.Is(err, first) {
t.Fatalf("expected first deny error, got %v", err)
}
@@ -215,7 +215,7 @@ func TestNotPolicyInvertsUserDenyButPreservesInternalErrors(t *testing.T) {
inverted := NotPolicy(func(ctx *MsgContext, data NoData) error {
return AsUserError(errors.New("denied"))
})
if err := inverted(&MsgContext{Logger: slog.CreateLogger()}, NoData{}); err != nil {
if err := inverted(&MsgContext{Logger: sneklog.CreateLogger()}, NoData{}); err != nil {
t.Fatalf("expected inverted deny to succeed, got %v", err)
}
@@ -223,7 +223,7 @@ func TestNotPolicyInvertsUserDenyButPreservesInternalErrors(t *testing.T) {
preserve := NotPolicy(func(ctx *MsgContext, data NoData) error {
return internal
})
err := preserve(&MsgContext{Logger: slog.CreateLogger()}, NoData{})
err := preserve(&MsgContext{Logger: sneklog.CreateLogger()}, NoData{})
if !errors.Is(err, internal) {
t.Fatalf("expected internal error to be preserved, got %v", err)
}
@@ -233,7 +233,7 @@ func TestRequirePolicyEmitsObserverEvents(t *testing.T) {
t.Run("allow", func(t *testing.T) {
observer := &recordingObserver{}
ctx := &MsgContext{
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
ctx: context.Background(),
observer: observer,
FromID: 10,
@@ -258,7 +258,7 @@ func TestRequirePolicyEmitsObserverEvents(t *testing.T) {
t.Run("deny", func(t *testing.T) {
observer := &recordingObserver{}
ctx := &MsgContext{
Logger: slog.CreateLogger(),
Logger: sneklog.CreateLogger(),
ctx: context.Background(),
observer: observer,
errorTemplate: "%s",
+4 -4
View File
@@ -7,7 +7,7 @@ import (
"testing"
"time"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
type runnerObserver struct {
@@ -17,7 +17,7 @@ type runnerObserver struct {
func TestExecRunnersRunsOnetimeSyncRunner(t *testing.T) {
var calls atomic.Int32
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
runners: []Runner[NoData]{
NewRunner("sync-once", func(*Bot[NoData]) error {
calls.Add(1)
@@ -39,7 +39,7 @@ func TestExecRunnersStopsBackgroundRunnerOnCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
runners: []Runner[NoData]{
NewRunner("background", func(*Bot[NoData]) error {
if calls.Add(1) == 1 {
@@ -71,7 +71,7 @@ func TestExecRunnersEmitObserverEvents(t *testing.T) {
wantErr := errors.New("runner failed")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
observer: observer,
runners: []Runner[NoData]{
NewRunner("sync-once", func(*Bot[NoData]) error {
+17 -17
View File
@@ -6,7 +6,7 @@ import (
"testing"
"git.scuroneko.dev/scuroneko/laniakea/tgapi"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
type failingSessionStore struct {
@@ -56,7 +56,7 @@ func TestBotAddPluginsPreservesScenesAndHandlesThem(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -149,7 +149,7 @@ func TestEnterSceneRejectsMissingEntryConfiguration(t *testing.T) {
plugin.NewScene("signup")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
}
@@ -172,7 +172,7 @@ func TestEnterSceneRejectsMissingEntryConfiguration(t *testing.T) {
plugin.NewScene("signup").SetEntry("start")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
}
@@ -231,7 +231,7 @@ func TestSceneCommandHandlerRunsBeforeStep(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -279,7 +279,7 @@ func TestSceneCommandObserverEmitsLifecycleEvents(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -331,7 +331,7 @@ func TestSceneStepObserverEmitsLifecycleEvents(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -386,7 +386,7 @@ func TestSceneMessageObserverEmitsLifecycleEvents(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -453,7 +453,7 @@ func TestScenePayloadHandlerRunsBeforeStep(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -509,7 +509,7 @@ func TestScenePayloadObserverEmitsLifecycleEvents(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -572,7 +572,7 @@ func TestSceneUnmatchedPayloadFallsThroughWithoutRunningStep(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
payloadType: BotPayloadJson,
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -648,7 +648,7 @@ func TestScenePassDoesNotPersistSessionData(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -724,7 +724,7 @@ func TestSceneUnmatchedCommandFallsThroughWithoutRunningStep(t *testing.T) {
}, "ping")
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -793,7 +793,7 @@ func TestSceneMessageFallbackRunsWhenNoCommandOrStepMatch(t *testing.T) {
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
prefixes: []string{"/"},
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
@@ -838,7 +838,7 @@ func TestSceneMessageFallbackRunsWhenNoCommandOrStepMatch(t *testing.T) {
func TestFindSceneSessionSupportsUserScopeWithoutMessage(t *testing.T) {
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
sessionStore: NewMemorySessionStore(),
sceneScopePriority: []SceneScope{SceneScopeUser, SceneScopeChat, SceneScopeUserChat},
}
@@ -865,7 +865,7 @@ func TestSceneStoreErrorsPropagate(t *testing.T) {
t.Run("find scene session get error", func(t *testing.T) {
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
sessionStore: failingSessionStore{getErr: getErr},
sceneScopePriority: []SceneScope{SceneScopeUser},
}
@@ -881,7 +881,7 @@ func TestSceneStoreErrorsPropagate(t *testing.T) {
return ctx.Stay(), nil
})
bot := &Bot[NoData]{
logger: slog.CreateLogger(),
logger: sneklog.CreateLogger(),
sessionStore: failingSessionStore{setErr: setErr},
sceneScopePriority: []SceneScope{SceneScopeUserChat, SceneScopeChat, SceneScopeUser},
}
+3 -3
View File
@@ -10,7 +10,7 @@ import (
"time"
"git.scuroneko.dev/scuroneko/laniakea/utils"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
// APIOpts holds configuration options for initializing the Telegram API client.
@@ -85,7 +85,7 @@ func (opts *APIOpts) SetLimiterDrop(b bool) *APIOpts {
type API struct {
token string
client *http.Client
logger *slog.Logger
logger *sneklog.Logger
useTestServer bool
apiUrl string
@@ -137,7 +137,7 @@ func (api *API) Close() error {
// GetLogger returns the internal logger for custom logging.
// See https://core.telegram.org/bots/api
func (api *API) GetLogger() *slog.Logger {
func (api *API) GetLogger() *sneklog.Logger {
return api.logger
}
+3 -3
View File
@@ -11,7 +11,7 @@ import (
"time"
"git.scuroneko.dev/scuroneko/laniakea/utils"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
const (
@@ -65,7 +65,7 @@ func (f UploaderFile) SetType(t UploaderFileType) UploaderFile {
// (InputFile/multipart). For JSON-only calls (file_id, URL, plain params), use API.
type Uploader struct {
api *API
logger *slog.Logger
logger *sneklog.Logger
}
// NewUploader creates a multipart uploader bound to an API client.
@@ -85,7 +85,7 @@ func (u *Uploader) Close() error { return u.logger.Close() }
// GetLogger returns uploader logger instance.
// See https://core.telegram.org/bots/api
func (u *Uploader) GetLogger() *slog.Logger { return u.logger }
func (u *Uploader) GetLogger() *sneklog.Logger { return u.logger }
// UploaderRequest is a low-level multipart upload request wrapper.
//
+7 -7
View File
@@ -3,22 +3,22 @@ package utils
import (
"os"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
// GetLoggerLevel returns DEBUG when DEBUG=true in env, otherwise FATAL.
func GetLoggerLevel() slog.LogLevel {
level := slog.FATAL
func GetLoggerLevel() sneklog.LogLevel {
level := sneklog.FATAL
if os.Getenv("DEBUG") == "true" {
level = slog.DEBUG
level = sneklog.DEBUG
}
return level
}
// CreateLogger creates a logger with the shared default policy:
// JSON stdout output, provided prefix, and provided level.
func CreateLogger(prefix string, level slog.LogLevel) *slog.Logger {
logger := slog.CreateLogger().Level(level)
func CreateLogger(prefix string, level sneklog.LogLevel) *sneklog.Logger {
logger := sneklog.CreateLogger().Level(level)
if prefix != "" {
logger.Prefix(prefix)
}
@@ -31,7 +31,7 @@ func CreateLogger(prefix string, level slog.LogLevel) *slog.Logger {
//
// The returned logger is always non-nil. When file writer creation fails, the
// logger still writes to stdout and the error is returned to the caller.
func CreateFileLogger(prefix string, level slog.LogLevel, filePath string) (*slog.Logger, error) {
func CreateFileLogger(prefix string, level sneklog.LogLevel, filePath string) (*sneklog.Logger, error) {
logger := CreateLogger(prefix, level)
fileWriter, err := logger.CreateTextFileWriter(filePath)
if err != nil {
+3 -3
View File
@@ -6,13 +6,13 @@ import (
"strings"
"testing"
"git.scuroneko.dev/scuroneko/slog"
"git.scuroneko.dev/scuroneko/sneklog/v2"
)
func TestCreateFileLoggerWritesToConfiguredFile(t *testing.T) {
logPath := filepath.Join(t.TempDir(), "main.log")
logger, err := CreateFileLogger("TEST", slog.DEBUG, logPath)
logger, err := CreateFileLogger("TEST", sneklog.DEBUG, logPath)
if err != nil {
t.Fatalf("CreateFileLogger returned error: %v", err)
}
@@ -28,7 +28,7 @@ func TestCreateFileLoggerWritesToConfiguredFile(t *testing.T) {
if !strings.Contains(string(data), "hello from file logger") {
t.Fatalf("expected log message in file, got %q", string(data))
}
if !strings.Contains(string(data), "[TEST]") {
if !strings.Contains(string(data), "TEST:") {
t.Fatalf("expected prefix in file, got %q", string(data))
}
}