Skip to content

Commit 188d16e

Browse files
authored
Merge pull request #86 from nao1215/nchika/logger
Add logger adapter
2 parents 2de0477 + 53d57af commit 188d16e

13 files changed

Lines changed: 992 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.10.0] - 2025-12-18
11+
12+
### Added
13+
- **Custom Logger Support**: Flexible logging system with slog integration
14+
- **`Logger` interface**: Simple logging interface with `Debug`, `Info`, `Warn`, `Error`, and `With` methods
15+
- **`ContextLogger` interface**: Extended logging interface with context-aware methods (`DebugContext`, `InfoContext`, `WarnContext`, `ErrorContext`)
16+
- **`NewSlogAdapter()`**: Adapter to use standard library `slog.Logger` with filesql's `Logger` interface
17+
- **`NewSlogContextAdapter()`**: Adapter for context-aware logging with `slog.Logger`
18+
- **`WithLogger()`**: Builder method to inject custom logger into the build and open process
19+
- **`nopLogger`**: Zero-overhead no-op logger implementation used as default (benchmarked at ~0.2 ns/op)
20+
- Logging throughout build, validation, and database opening operations
21+
- Comprehensive test coverage and benchmarks for all logger implementations
22+
23+
### Changed
24+
- **Documentation Updates**: Added Custom Logger section to all README files (7 languages: EN, ES, FR, JA, KO, RU, ZH-CN)
25+
- Usage examples with slog integration
26+
- Logger and ContextLogger interface definitions
27+
- Performance benchmark comparison table
28+
1029
## [0.9.0] - 2025-12-18
1130

1231
### Added
@@ -602,7 +621,8 @@ For users upgrading from v0.3.x:
602621
- Multi-language documentation (7 languages)
603622
- Standard database/sql interface implementation
604623

605-
[Unreleased]: https://github.com/nao1215/filesql/compare/v0.9.0...HEAD
624+
[Unreleased]: https://github.com/nao1215/filesql/compare/v0.10.0...HEAD
625+
[0.10.0]: https://github.com/nao1215/filesql/compare/v0.9.0...v0.10.0
606626
[0.9.0]: https://github.com/nao1215/filesql/compare/v0.8.0...v0.9.0
607627
[0.8.0]: https://github.com/nao1215/filesql/compare/v0.7.0...v0.8.0
608628
[0.7.0]: https://github.com/nao1215/filesql/compare/v0.6.0...v0.7.0

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,69 @@ parquetOptions := filesql.NewDumpOptions().
318318
// Note: Parquet export is implemented, but external compression is not supported (use Parquet's built-in compression)
319319
```
320320

321+
### Custom Logger
322+
323+
filesql supports pluggable logging via the `Logger` interface. By default, a no-op logger is used with zero performance overhead. You can inject your own logger (e.g., `slog`) for debugging and monitoring.
324+
325+
```go
326+
import (
327+
"log/slog"
328+
"os"
329+
"github.com/nao1215/filesql"
330+
)
331+
332+
// Create a slog logger
333+
slogLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
334+
Level: slog.LevelDebug,
335+
}))
336+
337+
// Wrap it with SlogAdapter and pass to the builder
338+
logger := filesql.NewSlogAdapter(slogLogger)
339+
340+
validatedBuilder, err := filesql.NewBuilder().
341+
WithLogger(logger).
342+
AddPath("data.csv").
343+
Build(ctx)
344+
```
345+
346+
#### Logger Interface
347+
348+
```go
349+
type Logger interface {
350+
Debug(msg string, args ...any)
351+
Info(msg string, args ...any)
352+
Warn(msg string, args ...any)
353+
Error(msg string, args ...any)
354+
With(args ...any) Logger
355+
}
356+
```
357+
358+
#### Context-Aware Logger
359+
360+
For context-aware logging, use `ContextLogger`:
361+
362+
```go
363+
type ContextLogger interface {
364+
Logger
365+
DebugContext(ctx context.Context, msg string, args ...any)
366+
InfoContext(ctx context.Context, msg string, args ...any)
367+
WarnContext(ctx context.Context, msg string, args ...any)
368+
ErrorContext(ctx context.Context, msg string, args ...any)
369+
}
370+
371+
// Use SlogContextAdapter for context-aware logging
372+
logger := filesql.NewSlogContextAdapter(slogLogger)
373+
```
374+
375+
#### Performance
376+
377+
| Logger Type | Performance | Memory |
378+
|-------------|-------------|--------|
379+
| nopLogger (default) | ~0.2 ns/op | 0 B/op |
380+
| SlogAdapter | ~1000 ns/op | ~630 B/op |
381+
382+
The default no-op logger has virtually zero overhead, making it safe to leave logging calls in production code.
383+
321384
## Table Naming Rules
322385

323386
filesql automatically derives table names from file paths:

builder.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type DBBuilder struct {
5353
autoSaveConfig *autoSaveConfig
5454
// defaultChunkSize is the default chunk size for reading large files (10MB)
5555
defaultChunkSize int
56+
// logger is the logger instance for internal logging
57+
logger Logger
5658

5759
// Internal processors for handling different responsibilities
5860
validator *validator
@@ -93,6 +95,7 @@ func NewBuilder() *DBBuilder {
9395
parsedTables: make([]*table, 0),
9496
autoSaveConfig: nil, // Default: no auto-save
9597
defaultChunkSize: chunkSize,
98+
logger: newNopLogger(), // Default: no-op logger
9699

97100
// Initialize internal processors
98101
validator: newValidator(),
@@ -164,6 +167,28 @@ func (b *DBBuilder) SetDefaultChunkSize(size int) *DBBuilder {
164167
return b
165168
}
166169

170+
// WithLogger sets a custom logger for internal operations.
171+
//
172+
// The logger interface is compatible with slog.Logger. You can use the provided
173+
// SlogAdapter to wrap an existing slog.Logger, or implement your own Logger.
174+
//
175+
// Examples:
176+
//
177+
// // Using slog
178+
// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
179+
// builder.WithLogger(filesql.NewSlogAdapter(logger))
180+
//
181+
// // Using a custom logger
182+
// builder.WithLogger(myCustomLogger)
183+
//
184+
// Returns self for chaining.
185+
func (b *DBBuilder) WithLogger(logger Logger) *DBBuilder {
186+
if logger != nil {
187+
b.logger = logger
188+
}
189+
return b
190+
}
191+
167192
// AddFS adds files from an embedded filesystem (go:embed).
168193
//
169194
// Automatically finds all CSV, TSV, and LTSV files in the filesystem.
@@ -254,6 +279,8 @@ func (b *DBBuilder) DisableAutoSave() *DBBuilder {
254279
//
255280
// Returns the same builder instance for method chaining, or an error if validation fails.
256281
func (b *DBBuilder) Build(ctx context.Context) (*DBBuilder, error) {
282+
b.logger.Debug("starting build", "paths", len(b.paths), "filesystems", len(b.filesystems), "readers", len(b.readers))
283+
257284
// Validate that we have at least one input
258285
if len(b.paths) == 0 && len(b.filesystems) == 0 && len(b.readers) == 0 {
259286
return nil, fmt.Errorf("%w: at least one path must be provided", ErrNoFiles)
@@ -290,6 +317,11 @@ func (b *DBBuilder) Build(ctx context.Context) (*DBBuilder, error) {
290317
return nil, err
291318
}
292319

320+
// Pass logger to internal processors
321+
b.streamProcessor.setLogger(b.logger)
322+
b.fileProcessor.setLogger(b.logger)
323+
324+
b.logger.Info("build completed", "collected_paths", len(b.collectedPaths), "readers", len(b.readers))
293325
return b, nil
294326
}
295327

@@ -311,6 +343,8 @@ func (b *DBBuilder) Build(ctx context.Context) (*DBBuilder, error) {
311343
//
312344
// Returns a *sql.DB connection or an error if the database cannot be created.
313345
func (b *DBBuilder) Open(ctx context.Context) (*sql.DB, error) {
346+
b.logger.Debug("opening database")
347+
314348
// Use validator to validate inputs availability
315349
if err := b.validator.validateInputsAvailable(b.collectedPaths, b.readers); err != nil {
316350
return nil, err
@@ -319,18 +353,22 @@ func (b *DBBuilder) Open(ctx context.Context) (*sql.DB, error) {
319353
// Use file processor to deduplicate compressed files
320354
b.collectedPaths = b.fileProcessor.deduplicateCompressedFiles(b.collectedPaths)
321355

356+
b.logger.Debug("creating in-memory database")
322357
db, err := b.createInMemoryDatabase()
323358
if err != nil {
359+
b.logger.Error("failed to create database", "error", err)
324360
return nil, err
325361
}
326362

327363
// Use stream processor for all streaming operations (now includes XLSX support)
328364
if err := b.streamProcessor.streamAllFilesToDatabase(ctx, db, b.collectedPaths); err != nil {
365+
b.logger.Error("failed to stream files", "error", err)
329366
_ = db.Close() // Ignore close error during error handling
330367
return nil, err
331368
}
332369

333370
if err := b.streamProcessor.streamAllReadersToDatabase(ctx, db, b.readers); err != nil {
371+
b.logger.Error("failed to stream readers", "error", err)
334372
_ = db.Close() // Ignore close error during error handling
335373
return nil, err
336374
}
@@ -345,6 +383,7 @@ func (b *DBBuilder) Open(ctx context.Context) (*sql.DB, error) {
345383
return nil, err
346384
}
347385

386+
b.logger.Info("database opened successfully")
348387
return db, nil
349388
}
350389

doc/es/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,69 @@ parquetOptions := filesql.NewDumpOptions().
317317
// Nota: La funcionalidad de exportación está implementada (compresión externa no soportada, use la compresión integrada de Parquet)
318318
```
319319

320+
### Logger Personalizado
321+
322+
filesql soporta logging conectable a través de la interfaz `Logger`. Por defecto, se usa un logger no-op con cero sobrecarga de rendimiento. Puedes inyectar tu propio logger (por ejemplo, `slog`) para depuración y monitoreo.
323+
324+
```go
325+
import (
326+
"log/slog"
327+
"os"
328+
"github.com/nao1215/filesql"
329+
)
330+
331+
// Crear un logger slog
332+
slogLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
333+
Level: slog.LevelDebug,
334+
}))
335+
336+
// Envolverlo con SlogAdapter y pasarlo al builder
337+
logger := filesql.NewSlogAdapter(slogLogger)
338+
339+
validatedBuilder, err := filesql.NewBuilder().
340+
WithLogger(logger).
341+
AddPath("data.csv").
342+
Build(ctx)
343+
```
344+
345+
#### Interfaz Logger
346+
347+
```go
348+
type Logger interface {
349+
Debug(msg string, args ...any)
350+
Info(msg string, args ...any)
351+
Warn(msg string, args ...any)
352+
Error(msg string, args ...any)
353+
With(args ...any) Logger
354+
}
355+
```
356+
357+
#### Logger con Contexto
358+
359+
Para logging con contexto, usa `ContextLogger`:
360+
361+
```go
362+
type ContextLogger interface {
363+
Logger
364+
DebugContext(ctx context.Context, msg string, args ...any)
365+
InfoContext(ctx context.Context, msg string, args ...any)
366+
WarnContext(ctx context.Context, msg string, args ...any)
367+
ErrorContext(ctx context.Context, msg string, args ...any)
368+
}
369+
370+
// Usa SlogContextAdapter para logging con contexto
371+
logger := filesql.NewSlogContextAdapter(slogLogger)
372+
```
373+
374+
#### Rendimiento
375+
376+
| Tipo de Logger | Rendimiento | Memoria |
377+
|----------------|-------------|---------|
378+
| nopLogger (por defecto) | ~0.2 ns/op | 0 B/op |
379+
| SlogAdapter | ~1000 ns/op | ~630 B/op |
380+
381+
El logger no-op por defecto tiene prácticamente cero sobrecarga, haciendo seguro dejar llamadas de logging en código de producción.
382+
320383
## Reglas de nomenclatura de tablas
321384

322385
filesql deriva automáticamente los nombres de las tablas de las rutas de archivo:

doc/fr/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,69 @@ parquetOptions := filesql.NewDumpOptions().
317317
// Note: L'exportation Parquet est implémentée (compression externe non supportée, utilisez la compression intégrée de Parquet)
318318
```
319319

320+
### Logger Personnalisé
321+
322+
filesql supporte le logging modulaire via l'interface `Logger`. Par défaut, un logger no-op est utilisé avec zéro surcharge de performance. Vous pouvez injecter votre propre logger (par exemple, `slog`) pour le débogage et la surveillance.
323+
324+
```go
325+
import (
326+
"log/slog"
327+
"os"
328+
"github.com/nao1215/filesql"
329+
)
330+
331+
// Créer un logger slog
332+
slogLogger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
333+
Level: slog.LevelDebug,
334+
}))
335+
336+
// L'envelopper avec SlogAdapter et le passer au builder
337+
logger := filesql.NewSlogAdapter(slogLogger)
338+
339+
validatedBuilder, err := filesql.NewBuilder().
340+
WithLogger(logger).
341+
AddPath("data.csv").
342+
Build(ctx)
343+
```
344+
345+
#### Interface Logger
346+
347+
```go
348+
type Logger interface {
349+
Debug(msg string, args ...any)
350+
Info(msg string, args ...any)
351+
Warn(msg string, args ...any)
352+
Error(msg string, args ...any)
353+
With(args ...any) Logger
354+
}
355+
```
356+
357+
#### Logger avec Contexte
358+
359+
Pour le logging avec contexte, utilisez `ContextLogger` :
360+
361+
```go
362+
type ContextLogger interface {
363+
Logger
364+
DebugContext(ctx context.Context, msg string, args ...any)
365+
InfoContext(ctx context.Context, msg string, args ...any)
366+
WarnContext(ctx context.Context, msg string, args ...any)
367+
ErrorContext(ctx context.Context, msg string, args ...any)
368+
}
369+
370+
// Utilisez SlogContextAdapter pour le logging avec contexte
371+
logger := filesql.NewSlogContextAdapter(slogLogger)
372+
```
373+
374+
#### Performance
375+
376+
| Type de Logger | Performance | Mémoire |
377+
|----------------|-------------|---------|
378+
| nopLogger (par défaut) | ~0.2 ns/op | 0 B/op |
379+
| SlogAdapter | ~1000 ns/op | ~630 B/op |
380+
381+
Le logger no-op par défaut a pratiquement zéro surcharge, rendant sûr de laisser les appels de logging dans le code de production.
382+
320383
## Règles de nommage des tables
321384

322385
filesql dérive automatiquement les noms de tables des chemins de fichiers :

0 commit comments

Comments
 (0)