Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement unified logging system with Zap and advanced features #507

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

tphakala
Copy link
Owner

@tphakala tphakala commented Mar 1, 2025

  • Add new logger package with Zap-based structured logging
  • Implement global and custom logger configurations
  • Support console and file logging with rotation
  • Add development and production logging modes
  • Introduce context-based logging with fields
  • Create comprehensive testing infrastructure for logger
  • Support JSON and human-readable log formats
  • Add color-coded log levels with optional disabling
  • Provide global logging functions and named loggers
  • Enhance cross-platform logging capabilities

This refactoring provides a more robust, performant, and flexible logging system for the application.

- Add new logger package with Zap-based structured logging
- Implement global and custom logger configurations
- Support console and file logging with rotation
- Add development and production logging modes
- Introduce context-based logging with fields
- Create comprehensive testing infrastructure for logger
- Support JSON and human-readable log formats
- Add color-coded log levels with optional disabling
- Provide global logging functions and named loggers
- Enhance cross-platform logging capabilities

This refactoring provides a more robust, performant, and flexible logging system for the application.
Copy link
Contributor

coderabbitai bot commented Mar 1, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (2)
  • internal/birdnet/data/labels/labels_lt.txt
  • internal/birdnet/data/labels/labels_lv.txt

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces extensive logging improvements across multiple packages by replacing standard logging with a structured, Zap‐based logger. Several new helper methods and updated function signatures now accept logger parameters, enhancing context and error reporting. The changes also include dependency updates in the module file, rework of API and server initialization to use custom logging, and comprehensive updates to database, disk management, and image provider modules. Additionally, new functionality for log rotation and sensitive data redaction is implemented along with expanded testing in the logger package.

Changes

File(s) Change Summary
go.mod Added new dependencies (go.uber.org/zap v1.27.0, gopkg.in/natefinch/lumberjack.v2 v2.2.1), updated go.uber.org/multierr from v1.9.0 to v1.11.0, and removed go.uber.org/atomic v1.9.0.
internal/httpcontroller/handlers/...
(audio_level_sse.go, handlers.go, media.go, detections.go, sse.go, middleware.go, template_renderers.go, dashboard.go, utils.go)
Replaced standard logging with a custom structured logger; added helper methods (e.g., getAudioConnectionLogger, LogDebug, SetLogger, etc.); updated method signatures to include logger parameters; removed legacy logging functions.
internal/httpcontroller/server.go, internal/httpcontroller/server_logger.go Enhanced server initialization with a new constructor (NewWithLogger), integrated custom logger via an echoLogAdapter, and improved error reporting and request logging.
internal/logger/...
(README.md, config.go, logger.go, logger_test.go, rotation.go, testing.go, sensitive.go, sensitive_test.go)
Overhauled the logger package using Zap for structured logging; introduced new configuration and rotation functionalities; added testing support and sensitive data redaction; removed legacy exported logger APIs and file handling utilities.
internal/api/v2/...
(api.go, integrations.go, streams.go, weather.go)
Transitioned API logging from standard to structured logging; modified API controller initialization to accept a custom logger; added helper methods (e.g., LogfError, LogfWarn, etc.) and updated logging statements in MQTT, stream handling, and weather data retrieval.
internal/analysis/...
(realtime.go, birdnet_init.go)
Integrated structured logging into real-time analysis; updated function signatures to pass logger parameters; maintained error logging in BirdNET initialization.
internal/datastore/...
(interfaces.go, manage.go, mysql.go, sqlite.go)
Added a Logger field and SetLogger method to the DataStore; updated migration and database operations for improved error logging; modified MySQL's UpdateNote signature and restructured SQLite backup and connection logic.
internal/diskmanager/...
(file_utils.go, policy_age.go, policy_usage.go, README.md)
Introduced a new DiskManager struct with integrated structured logging; refactored cleanup policies (age-based and usage-based) into methods; enhanced logging for disk usage monitoring and retention policy enforcement; updated documentation accordingly.
internal/imageprovider/...
(imageprovider.go, wikipedia.go)
Updated image provider components to use the custom structured logger; added component-specific logging for image caching and Wikipedia media fetching; revised logger initialization in provider functions.
cmd/...
(root.go, realtime/realtime.go)
Minor comment update in the root command; updated real-time command initialization comments to indicate that the real-time analysis now handles its own logger initialization.
internal/api/v2/integration.go Deleted legacy integration file that previously handled API endpoint initialization.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SSEHandler
    participant Logger

    Client->>SSEHandler: Establish SSE connection
    SSEHandler->>Logger: Create connection-specific logger
    Logger-->>SSEHandler: Log "Connection established"
    SSEHandler->>SSEHandler: Add client with connectionID & clientIP
    Client->>SSEHandler: Send heartbeat/data
    SSEHandler->>Logger: Log activity and check for timeouts
    SSEHandler->>Logger: Log client removal on disconnect
Loading
sequenceDiagram
    participant APIController
    participant Config
    participant Logger
    participant Echo

    APIController->>Logger: Initialize structured logger
    APIController->>Config: Retrieve API settings
    APIController->>Echo: Setup routes with provided logger
    Echo-->>APIController: Return initialized routes
    APIController->>Logger: Log errors and shutdown events
Loading

Poem

In my burrow of bytes, I hop with glee,
Structured logs now guide each key,
With carrots of code and debugging delight,
I leap over errors in the soft moonlight,
Hopping on updates with rhythm so true,
Celebrating changes as a wise rabbit do!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@tphakala tphakala added the enhancement New feature or request label Mar 1, 2025
@tphakala tphakala self-assigned this Mar 1, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (14)
internal/httpcontroller/handlers/media.go (1)

9-9: Consider removing standard log package import

The standard log package is still imported but most logging has been migrated to structured logging. However, it's still used in ThumbnailAttribution (line 211). Consider migrating that remaining usage to the structured logger as well for consistency.

internal/logger/README.md (2)

37-47: Recommend highlighting concurrency considerations for the global logger.
Although the documentation shows how to use the global logger, consider adding a note that the global logger is safe for concurrent usage due to Zap’s design. This clarifies for newcomers that locks or additional synchronization aren't required in typical usage.


186-187: Refine punctuation for clarity.
In the bullet list referencing CreateTestCore and other test helpers, consider adjusting punctuation to align with standard style guides and improve readability (e.g., add a full stop or consistently format as a sentence).

🧰 Tools
🪛 LanguageTool

[uncategorized] ~187-~187: Loose punctuation mark.
Context: ...nctions for testing: - CreateTestCore: Creates a logger core that writes to a ...

(UNLIKELY_OPENING_PUNCTUATION)

internal/logger/config.go (2)

24-34: Clarify default log level in DefaultConfig.
When Level is left empty, the actual log level will be determined only at runtime by the calling code (e.g., in getZapLevel). Consider documenting in the comment that the default effectively becomes "debug" if Development is true, to help future maintainers quickly understand the intended default behavior.


70-81: Log invalid log-level inputs.
Currently, getZapLevel quietly falls back to the default level if parsing fails. Consider logging a warning or an informational message when the user-supplied log level is invalid, to avoid confusion when typos appear in configuration.

internal/httpcontroller/server_logger.go (2)

19-25: Support more granular Echo log levels.
Right now, all Echo log messages are mapped to .Info(). For more refined logging, consider parsing or mapping Echo’s log levels to the appropriate Zap levels (e.g., .Debug(), .Warn(), .Error()) so critical logs stand out.


103-125: Vary logging level based on status codes in Echo request logs.
Currently, every request log is recorded at the Info level, even if the status code indicates a client or server error (e.g., 400 or 500). Consider elevating the logging level for 4xx/5xx to .Warn() or .Error() for better visibility into issues.

internal/httpcontroller/server.go (2)

93-98: Default server settings look good.
Providing a default web server port of "8080" is a sensible fallback. Consider logging a message when defaults take effect, so users are aware they're running on port 8080.

 func configureDefaultSettings(settings *conf.Settings) {
     if settings.WebServer.Port == "" {
+        log.Printf("No port specified; defaulting to 8080")
         settings.WebServer.Port = "8080"
     }
 }

213-221: Logging server errors with structured fields is beneficial.
This method properly leverages s.Logger, falling back to log.Printf. Ensure the channel is properly closed or consumed to prevent goroutine leaks.

internal/logger/logger_test.go (1)

54-71: Consider naming returned results in captureOutput for clarity.

The static analysis tools flag unnamed return results in this function. Naming them can improve readability and help others quickly see what’s being returned.

Possible improvement:

-func captureOutput(t *testing.T) (*bytes.Buffer, func()) {
+func captureOutput(t *testing.T) (buf *bytes.Buffer, restore func()) {
     var localBuf bytes.Buffer
     ...
     return &localBuf, func() {
         ...
     }
}
🧰 Tools
🪛 GitHub Check: golangci / lint

[failure] 54-54:
unnamedResult: consider giving a name to these results (gocritic)

🪛 GitHub Check: lint

[failure] 54-54:
unnamedResult: consider giving a name to these results (gocritic)

internal/logger/logger.go (2)

102-107: Consider removing or limiting special characters/emojis in production logs.

While these logs are helpful during development, emojis might not be desirable in some production environments. You could wrap them conditional on dev mode or remove them entirely.


122-124: Combine append calls for cleaner code.

The linter flagged these chained append calls. Consider merging them for brevity.

 if !config.DisableCaller {
-    opts = append(opts, zap.AddCaller())
-    opts = append(opts, zap.AddCallerSkip(1))
+    opts = append(opts, zap.AddCaller(), zap.AddCallerSkip(1))
 }
🧰 Tools
🪛 GitHub Check: golangci / lint

[failure] 123-123:
appendCombine: can combine chain of 2 appends into one (gocritic)

🪛 GitHub Check: lint

[failure] 123-123:
appendCombine: can combine chain of 2 appends into one (gocritic)

🪛 GitHub Actions: golangci-lint

[warning] 123-123: appendCombine: can combine chain of 2 appends into one (gocritic)

🪛 GitHub Actions: golangci-test

[warning] 123-123: appendCombine: can combine chain of 2 appends into one (gocritic)

🪛 GitHub Actions: Build docker image

[warning] 123-123: appendCombine: can combine chain of 2 appends into one (gocritic)

internal/httpcontroller/handlers/handlers.go (2)

82-90: Enforce consistent debug-level logging patterns.

The fallback to baseHandler.logInfo("DEBUG: ...") might be confusing in certain log aggregators, as it merges debug logs into the info stream. Consider a minimal approach or disabling debug logs entirely if the custom logger is unavailable.


109-128: Encourage distinct error handling paths.

Merging both “error logging with fields” and “fallback standard logger” in one block is acceptable, but can be more readable when separated into smaller blocks or helper methods, especially if error details grow. This also helps avoid large if-else trees.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80d872a and ac35a01.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (15)
  • go.mod (2 hunks)
  • internal/httpcontroller/handlers/audio_level_sse.go (3 hunks)
  • internal/httpcontroller/handlers/handlers.go (6 hunks)
  • internal/httpcontroller/handlers/media.go (13 hunks)
  • internal/httpcontroller/server.go (4 hunks)
  • internal/httpcontroller/server_logger.go (1 hunks)
  • internal/logger/README.md (1 hunks)
  • internal/logger/config.go (1 hunks)
  • internal/logger/exported.go (0 hunks)
  • internal/logger/filehandler.go (0 hunks)
  • internal/logger/global.go (1 hunks)
  • internal/logger/logger.go (1 hunks)
  • internal/logger/logger_test.go (1 hunks)
  • internal/logger/rotation.go (1 hunks)
  • internal/logger/testing.go (1 hunks)
💤 Files with no reviewable changes (2)
  • internal/logger/exported.go
  • internal/logger/filehandler.go
🧰 Additional context used
🪛 LanguageTool
internal/logger/README.md

[uncategorized] ~187-~187: Loose punctuation mark.
Context: ...nctions for testing: - CreateTestCore: Creates a logger core that writes to a ...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 GitHub Check: golangci / lint
internal/logger/logger.go

[failure] 123-123:
appendCombine: can combine chain of 2 appends into one (gocritic)

internal/logger/logger_test.go

[failure] 54-54:
unnamedResult: consider giving a name to these results (gocritic)

🪛 GitHub Check: lint
internal/logger/logger.go

[failure] 123-123:
appendCombine: can combine chain of 2 appends into one (gocritic)

internal/logger/logger_test.go

[failure] 54-54:
unnamedResult: consider giving a name to these results (gocritic)

🪛 GitHub Actions: golangci-lint
internal/logger/logger.go

[warning] 123-123: appendCombine: can combine chain of 2 appends into one (gocritic)

🪛 GitHub Actions: golangci-test
internal/logger/logger.go

[warning] 123-123: appendCombine: can combine chain of 2 appends into one (gocritic)

🪛 GitHub Actions: Build docker image
internal/logger/logger.go

[warning] 123-123: appendCombine: can combine chain of 2 appends into one (gocritic)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (28)
internal/httpcontroller/handlers/audio_level_sse.go (2)

151-151: Great use of structured logging!

The transition from standard logging to structured logging with key-value pairs provides better context and readability. Including the client_ip field helps with debugging and tracking connection issues.


198-205: Smart implementation of rate-limited logging

Limiting debug logs for audio data to once every 5 seconds is an excellent optimization to prevent log spam while still providing visibility into the system's operation.

go.mod (3)

32-32: Appropriate logging library choice

Adding Zap as a dependency is a good choice for structured logging. It's high-performance and widely adopted in the Go ecosystem.


37-37: Good addition for log rotation

Including lumberjack for log rotation complements the new logging system and aligns with the PR objectives for a comprehensive logging solution.


97-97: Dependency version update

Updating to a newer version of multierr aligns with using the latest compatible versions of the Zap ecosystem.

internal/httpcontroller/handlers/media.go (2)

37-39: Good practice for configurable debug verbosity

Adding a toggle for media debug logging is a smart implementation that allows for fine-grained control over log verbosity, especially useful since media operations can generate verbose logs.


41-47: Well-implemented conditional logging method

The mediaDebug method properly checks both that debug logging is enabled and that a logger is available before attempting to log, which prevents potential nil pointer dereferences.

internal/logger/testing.go (2)

11-45: Well-designed test utility for capturing log output

The CreateTestCore function is a valuable testing utility that allows for intercepting logger output during tests. The implementation correctly handles configuration options like log levels, JSON formatting, and color output.


47-69: Good implementation of custom logger creation for tests

The NewLoggerWithCore function provides flexibility for testing by allowing injection of custom core behavior. The proper setup of options like caller information and development mode is handled well.

internal/httpcontroller/server.go (4)

8-8: No issues with the new import usage.
The import of "os" is straightforward and aligns with environment-related functionality introduced below.


84-88: Conditional logger injection is appropriate.
This check ensures that the logger is only set after it is properly initialized. However, ensure that any downstream usage of s.Handlers.Logger gracefully handles a nil logger if initialization fails.


129-129: Ensure errChan is closed on shutdown.
Spawning a goroutine to consume errors is fine, but verify that errChan is closed on graceful shutdown to avoid hangs. Otherwise, for err := range errChan in handleServerError could block indefinitely if the channel is never closed.


201-205: Environment-driven dev mode check is valid.
Your fallback to Settings.WebServer.Debug is a clear and concise approach. If you ever plan to allow explicit "false" environment variable values (e.g. "0", "false", etc.), you already handle them by returning the config setting.

Also applies to: 208-209

internal/logger/rotation.go (4)

1-13: Imports and package definition look standard.
All required dependencies (zap, zapcore, lumberjack) are correctly included. There are no security or concurrency issues in these import statements.


15-38: CreateRotatingFileCore is clear and handles setup robustly.
The function correctly ensures the directory exists and configures rotation. Consider documenting the behavior when MkdirAll fails to avoid confusion in calling code.


40-91: NewRotatingLogger: good logic for rotating logs.
Your defaulting to non-color output for file-based logs is wise. The calls to zap.AddCaller() and zap.AddCallerSkip(1) are typical for removing the logger frame from logs, but ensure logs reference the correct call site.


93-105: NewRotatingCoreLogger provides a convenient wrapper.
Wrapping the low-level Zap logger and sugar logger inside your own Logger struct is a neat encapsulation. Overall, this approach is consistent with the rest of the logging framework.

internal/logger/global.go (4)

1-15: Global logger variables are well-structured.
Using sync.Once to guard initialization helps avoid concurrency issues. The errDefaultLogger is also well-handled.


17-24: InitGlobal ensures a one-time setup.
If multiple attempts to initialize occur, only the first call matters. This is a sensible approach for a global logger to prevent re-initialization races.


26-39: GetGlobal default fallback is practical.
If the logger is never explicitly initialized, creating a default that writes to stdout with a development-friendly config is a good fail-safe. The immediate exit on initialization failure is also a valid choice, as the app may not be viable without logging.


41-44: Global logging helpers are convenient.
The direct calls (Debug, Info, etc.) with GetGlobal() reduce boilerplate for clients. The approach is consistent with other global logging patterns, though keep in mind that some prefer explicit logger injection to avoid global state.

Also applies to: 46-49, 51-54, 56-59, 61-64, 66-69, 71-74, 76-79

internal/logger/logger_test.go (2)

73-169: Validate edge cases for log level transitions.

While the test coverage for different log levels is comprehensive, consider additional edge cases, such as:

  • Setting an invalid log level in the Config to confirm it defaults to a known level.
  • Testing corner cases like “fatal” level for potential abrupt program termination.

379-443: Great test coverage for context fields.

The code effectively verifies that all context fields are preserved and logged as expected. This thorough approach prevents context leakage issues.

internal/logger/logger.go (2)

2-10: Excellent package-level documentation.

The top-level doc comment clearly conveys the purpose, usage, and features of the logging system. This aids maintainability and onboarding.


264-267: Check for potential Sync errors during logger shutdown.

Currently, the return value of l.zap.Sync() is returned but not always acted upon. In some production scenarios, capturing and handling sync errors might be useful to ensure logs are fully flushed or to report I/O issues.

internal/httpcontroller/handlers/handlers.go (3)

40-40: Praise for introducing a dedicated field for the custom logger.

Adding Logger *logger.Logger ensures consistent and structured logging across all handlers while preserving backward compatibility.


163-167: Convenient setter for custom logger.

Exposing SetLogger allows dynamic logger injection, which is valuable for testing or reconfiguration. Ensure this method is called before critical logging statements.


205-215: High-quality contextual logging of HTTP errors.

Including stacktrace, code, and message fields is ideal for quick troubleshooting. The approach ensures that essential debugging info is captured in the logs.

tphakala added 5 commits March 2, 2025 12:19
…lity

- Simplify logger configuration by combining Zap caller options
- Remove `captureOutput` utility function from logger test file
- Improve code conciseness and reduce unnecessary complexity
…ntext

- Add connection-specific logging for SSE and audio level streams
- Implement detailed logging with unique connection IDs and client IPs
- Improve log granularity for audio source activity and level changes
- Introduce structured logging methods for SSE handler
- Add connection metrics and performance tracking
- Refactor logging to support more informative debug and error messages

This update significantly improves observability and debugging capabilities for server-sent events and audio level monitoring.
…ture

- Break down AudioLevelSSE method into smaller, focused methods
- Introduce ConnectionState struct to manage connection-specific data
- Extract connection handling, logging, and event processing into separate methods
- Improve code readability and maintainability
- Simplify error handling and logging across SSE connection lifecycle
- Update error logging in detections handler to match new logging pattern

This refactoring enhances the code organization and makes the SSE handler more modular and easier to understand.
…e approach

- Add request-specific logging with unique request IDs and client IPs
- Implement component-specific middleware loggers
- Improve logging granularity for CSRF, cache control, and authentication middleware
- Add conditional logging based on development mode
- Integrate with existing logger infrastructure
- Enhance error and debug message context

This refactoring improves middleware observability and provides more detailed logging for request processing.
… path handling

- Refactor media-related handlers to use structured, component-specific logging
- Improve clip name sanitization with granular, modular path validation methods
- Add detailed debug logging for spectrogram and audio clip serving
- Implement OS-agnostic path handling with improved error context
- Break down complex path sanitization logic into smaller, focused methods
- Add comprehensive logging for file existence checks and path resolution

This refactoring improves code readability, error handling, and observability in media-related operations.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (11)
internal/httpcontroller/handlers/detections.go (1)

192-192: Consider using structured fields instead of embedding the clip name in the message.
Logging frameworks like Zap excel at capturing contextual data. Rather than interpolating note.ClipName into the message string, you could pass it as a separate field for more structured logs and better filtering.

internal/httpcontroller/handlers/media.go (1)

38-41: Remove or unify the unused debugLogging variable.
This global variable appears to be overshadowed by h.debug checks in various functions. If the intent is to toggle debugging at runtime, ensure this is referenced consistently, or remove it if you prefer the handler’s h.debug logic.

internal/httpcontroller/middleware.go (1)

45-50: Avoid truncating the request ID to prevent potential collisions at scale.
Using only part of the UUID ([:8]) may cause collisions with large volumes of requests. Consider using the full or at least a longer substring of the UUID to ensure globally unique correlation IDs.

internal/logger/logger.go (2)

80-97: Consistent logging methods.

Providing separate functions (Info, Warn, Error, Fatal) aligns well with structured logging patterns. Just be cautious with using Fatal in libraries, as it calls os.Exit(1) and might disrupt hosting applications if triggered unexpectedly.


99-243: Well-structured logger initialization.

NewLogger covers multiple configuration pathways (console, file, tee logger) and includes development options and rotation logic. Good job ensuring directories exist before file logging. Consider clarifying what happens if a rotation config is provided for development mode—some teams might expect rotation in dev mode as well.

internal/httpcontroller/handlers/sse.go (3)

67-183: Robust SSE event loop.

Switch statements handle potential logging with different logger references or debug fallback, preventing null-pointer issues. The heartbeat approach ensures clients remain connected. Consider implementing logic to periodically remove stale or blocked channels if necessary for resource cleanup.


184-242: Broadcast notifications with flexible logger naming.

SendNotification categorizes logs by notification type, enabling more granular filtering. The logic for skipping blocked channels is commented as an option—this is a good place to add metrics or counters for repeated blocking.


296-306: Deprecated debug function still present.

The plain Debug function is labeled deprecated but still available. Schedule a plan to remove it or convert existing calls to the new structured logging methods for consistency.

internal/httpcontroller/handlers/audio_level_sse.go (3)

57-63: Consider using the built-in math.Abs function.
Your custom abs function replicates math.Abs; switching to the Go standard library might reduce code duplication.

-import "strings"
+import (
+	"strings"
+	"math"
+	...
+)

-func abs(x float64) float64 {
-  if x < 0 {
-    return -x
-  }
-  return x
-}
+// Use math.Abs in place of abs(...)

133-214: Refactor repeated name-setting logic to reduce duplication.
The code to assign audioData.Name is somewhat duplicated from the initialization logic. Extracting it into a helper could simplify maintenance.


255-283: Potential UUID collision nitpick.
Truncating the full UUID to 8 characters is unlikely to collide, but still possible. Consider using a longer substring or the entire UUID if collisions would be problematic.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac35a01 and 874beb0.

📒 Files selected for processing (11)
  • internal/httpcontroller/handlers/audio_level_sse.go (4 hunks)
  • internal/httpcontroller/handlers/detections.go (1 hunks)
  • internal/httpcontroller/handlers/handlers.go (7 hunks)
  • internal/httpcontroller/handlers/media.go (9 hunks)
  • internal/httpcontroller/handlers/sse.go (4 hunks)
  • internal/httpcontroller/middleware.go (7 hunks)
  • internal/httpcontroller/server.go (5 hunks)
  • internal/httpcontroller/server_logger.go (1 hunks)
  • internal/httpcontroller/template_renderers.go (7 hunks)
  • internal/logger/logger.go (1 hunks)
  • internal/logger/logger_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/httpcontroller/server_logger.go
  • internal/httpcontroller/server.go
  • internal/httpcontroller/handlers/handlers.go
🧰 Additional context used
🪛 GitHub Check: golangci / lint
internal/httpcontroller/template_renderers.go

[failure] 143-143:
dynamicFmtString: use errors.New(errMsg) or fmt.Errorf("%s", errMsg) instead (gocritic)


[failure] 165-165:
dynamicFmtString: use errors.New(errMsg) or fmt.Errorf("%s", errMsg) instead (gocritic)

🪛 GitHub Check: lint
internal/httpcontroller/template_renderers.go

[failure] 143-143:
dynamicFmtString: use errors.New(errMsg) or fmt.Errorf("%s", errMsg) instead (gocritic)


[failure] 165-165:
dynamicFmtString: use errors.New(errMsg) or fmt.Errorf("%s", errMsg) instead (gocritic)

🪛 GitHub Actions: golangci-test
internal/httpcontroller/template_renderers.go

[error] 143-143: dynamicFmtString: use errors.New(errMsg) or fmt.Errorf("%s", errMsg) instead (gocritic)

🪛 GitHub Actions: golangci-lint
internal/httpcontroller/template_renderers.go

[error] 143-143: dynamicFmtString: use errors.New(errMsg) or fmt.Errorf("%s", errMsg) instead (gocritic)

🪛 GitHub Actions: Build docker image
internal/httpcontroller/template_renderers.go

[error] 143-143: dynamicFmtString: use errors.New(errMsg) or fmt.Errorf("%s", errMsg) instead (gocritic)

🪛 GitHub Check: CodeQL
internal/logger/logger.go

[failure] 76-76: Clear-text logging of sensitive information
Sensitive data returned by HTTP request headers flows to a logging call.

🔇 Additional comments (40)
internal/logger/logger_test.go (9)

19-47: Implementation of helper classes looks good.

Your testingWriter and testLogCapture provide a straightforward way to capture logs in memory and within test logs. There's no concurrency concern here since the buffer is internal and only manipulated within the test context.


48-51: Simple and effective in-memory filesystem setup.

The setupTestFs function is concise and uses Afero for an in-memory filesystem, which is beneficial for fast and isolated tests.


53-149: Thorough test coverage for log levels.

The table-driven approach in TestLogLevels effectively checks each level's behavior, ensuring logs are included or excluded as expected. The use of NewTestLogCapture and field checks is robust.


151-248: Comprehensive format testing.

TestLogFormatting verifies both console and JSON output formats, along with validating JSON structure. This ensures reliable log parsing and matches your intended output structure.


250-310: Dual testing approach for file output.

The combination of an in-memory fs and a real filesystem test in TestFileOutput is commendable. It adequately covers different scenarios without risking disk pollution in CI environments.


312-357: Pragmatic rotation test.

TestRotation checks log file creation and basic rotation configuration. Skipping forced rotation in tests is a sensible compromise to avoid bloated test data.


359-423: Context logging test is well-designed.

TestLoggerWithContext thoroughly verifies that contextual fields are included in the final log output. The JSON parsing step is especially helpful in preventing regressions in structured logging.


425-466: Named logger coverage is sufficient.

TestLoggerNaming confirms that custom logger names are accurately captured in the log output. This is crucial for distinguishing logs from different components.


468-503: Global logger test ensures shared state correctness.

TestGlobalLogger verifies that global initialization and usage work together smoothly. Redirecting stdout is a neat trick for capturing output without polluting CI logs.

internal/logger/logger.go (6)

2-72: Encoder configuration design is clear and flexible.

The createEncoderConfig and simpleDateTimeEncoder functions keep your encoder consistent and easy to maintain. Using a human-readable timestamp without milliseconds is a sensible choice for many logging scenarios.


74-78: Potential risk of logging sensitive data.

This code uses l.sugar.Debugw(msg, fields...). Ensure no sensitive or PII data is passed in these fields, especially if debug logs might be enabled in production.

🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 76-76: Clear-text logging of sensitive information
Sensitive data returned by HTTP request headers flows to a logging call.


245-252: Named logger usage.

The Named method provides a new Logger instance while preserving the underlying configuration. This is especially useful for categorizing logs by subsystem or feature.


254-261: Custom fields for context.

With method allows you to attach fields that persist across log calls, promoting consistent structured output.


263-266: Log flushing included.

Exposing Sync ensures log entries aren't lost on abrupt application exit. Make sure to call it where it's critical (e.g., at shutdown).


268-272: Direct Zap logger access.

GetZapLogger helps integrate with third-party libraries needing direct zap APIs. It's good to maintain a single source of truth for logging.

internal/httpcontroller/handlers/sse.go (7)

34-48: Well-defined SSEHandler initialization.

NewSSEHandler neatly configures a named logger if provided. Logging subcomponents under "sse.handler" will help isolate SSE logs.


50-62: Connection-scoped logging approach.

getConnectionLogger appends both a fixed “conn” name layer and connection-specific fields. This is a great way to track events for each client connection without clutter in global logs.


244-270: Synchronized client addition.

addClient is properly guarded by a mutex while preserving debug statements or structured logs. The channel buffer usage is disclosed in logs for easier debugging.


272-294: Graceful client removal and logging.

removeClient ensures channels are closed safely, signals disconnection in logs, and updates in-memory structures under lock. This helps maintain SSE session integrity.


308-321: New structured LogDebug function is beneficial.

LogDebug centralizes debug logic, bridging the structured logger and fallback log.Printf usage. This helps keep debug logs consistent across different environments.


323-334: Clear info-level logging.

LogInfo seamlessly integrates structured logs while providing a graceful fallback. This fosters consistent SSE info-level messages even without a configured logger instance.


336-351: Error logging with optional error object.

LogError merges the structured approach with standard logging. The appended "error", err field ensures error details are never lost. Good fallback logic for the no-logger case.

internal/httpcontroller/handlers/audio_level_sse.go (18)

7-7: No issues with importing "strings".
The addition of "strings" appears consistent with the RTSP-trimming logic.


11-11: UUID import is appropriate.
Using "github.com/google/uuid" is a standard choice for generating connection IDs.


13-13: Structured logging import looks good.
Importing "github.com/tphakala/birdnet-go/internal/logger" aligns with the new logging system requirements.


23-35: Connection-specific logger creation is well-designed.
Providing contextual fields such as connection ID and client IP is a good practice for traceability. Returning nil if h.Logger is absent ensures safe handling.


36-55: Source-specific logger logic is clear.
Naming sub-loggers by source type (malgo, rtsp, etc.) helps maintain a meaningful logging hierarchy.


218-249: Activity check implementation is straightforward.
Looping through sources, marking them as inactive when needed, and logging changes is clear and well-structured.


285-295: ConnectionState struct is logically organized.
Storing times and counters here helps simplify SSE state management.


297-308: One-connection-per-IP policy might hamper legitimate multi-device usage.
Consider whether you want strictly one SSE connection per IP. If multiple devices share a NAT, they may be rejected.


310-317: Connection establishment logging is clear.
Having a dedicated method for logging new connections ensures consistency and readability.


319-327: Cleanup logic is succinct.
Removing the active connection and logging closure is straightforward.


329-342: Initialization of connection state looks good.
Properly sets up the needed maps and timestamps, avoiding uninitialized usage in later steps.


344-352: Error logging method is consistent.
Centralizing err logs to either the connection logger or the global logger is a clean approach.


353-392: Main SSE event loop is well-structured.
Handling timeouts, heartbeats, and incoming audio data in a select block suits SSE concurrency patterns.


394-404: Timeout handling is appropriate.
Stopping on a timed-out connection helps prevent ghost connections from lingering.


406-416: Client disconnect logic is straightforward.
Logs relevant metrics before exiting.


418-453: Rate-limited updates and selective logging are well done.
Sending SSE updates every 50ms avoids spamming the client, and the 5-second log interval avoids excessive log traffic.


455-468: Periodic activity checks help prevent stale data.
Marking inactive sources and pushing updates only when needed is efficient.


470-489: Heartbeat and connection stats.
Sending comment-based heartbeats keeps the SSE connection alive, and occasional stats logging is a useful touch.

tphakala added 3 commits March 2, 2025 13:11
…roved error handling

- Add component-specific logging for detection-related handlers
- Implement getDetectionLogger method for granular, context-aware logging
- Refactor Debug method to support structured logging
- Improve error logging in DetectionDetails, RecentDetections, and DeleteDetection methods
- Add conditional logging based on debug mode and logger availability
- Remove redundant Debug method from utils.go

This refactoring improves logging consistency, error traceability, and observability in detection-related operations.
…roved error handling

- Add component-specific logging for database operations in interfaces.go
- Implement SetLogger and getDbLogger methods for granular, context-aware logging
- Refactor database connection and migration methods to support custom logging
- Improve error logging and retry mechanisms in SQLite and MySQL stores
- Add detailed debug logging for database transactions, locks, and migrations
- Enhance logging consistency across datastore implementations

This refactoring improves database operation observability, error traceability, and provides more informative logging for database interactions.
…ime analysis

- Replace standard log with structured logging in diskmanager package
- Add logger initialization for disk-related operations
- Improve error and debug logging in age and usage-based cleanup policies
- Refactor file sorting and cleanup methods to use structured logging
- Add more detailed context to log messages for better observability
- Ensure consistent logging approach across diskmanager components

This refactoring improves logging granularity and error traceability in disk management and cleanup processes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (5)
internal/analysis/realtime.go (5)

112-114: 🛠️ Refactor suggestion

Inconsistent logging approach.

These lines still use the standard log.Printf while the rest of the file uses the new structured logging system.

-			log.Printf("⚠️  Error initializing buffers: %v", err)
-			log.Println("⚠️  Some audio sources might not be available.")
+			logger.GetGlobal().Error("Error initializing buffers", "error", err)
+			logger.GetGlobal().Warn("Some audio sources might not be available")

153-153: 🛠️ Refactor suggestion

Inconsistent logging approach.

Similar to the previous issue, this line still uses the standard logging.

-		log.Println("⚠️  Starting without active audio sources. You can configure audio devices or RTSP streams through the web interface.")
+		logger.GetGlobal().Warn("Starting without active audio sources. You can configure audio devices or RTSP streams through the web interface")

258-260: 🛠️ Refactor suggestion

Inconsistent logging approach.

This section still uses standard logging which should be updated to the new structured logging.

-		log.Println("Received Ctrl+C, shutting down")
-		close(quitChan) // Close the quit channel to signal other goroutines to stop
+		logger.GetGlobal().Info("Received Ctrl+C, shutting down")
+		close(quitChan) // Close the quit channel to signal other goroutines to stop

265-269: 🛠️ Refactor suggestion

Inconsistent logging approach.

The database closing logs should also use the structured logging approach.

-		log.Printf("Failed to close database: %v", err)
+		dbLogger.Error("Failed to close database", "error", err)
-		log.Println("Successfully closed database")
+		dbLogger.Info("Successfully closed database")

312-313: 🛠️ Refactor suggestion

Inconsistent logging approach.

These log statements should also be converted to use the structured logging system for consistency.

Consider creating and using a named logger for the image cache component to maintain consistency with the rest of the refactoring.

Also applies to: 319-320, 359-365

🧹 Nitpick comments (10)
cmd/root.go (1)

76-93: Consider validating logger configuration values.

While the implementation is solid, consider adding validation for the log level and file path before initializing the logger. This could prevent runtime errors if invalid values are provided.

func initialize() error {
	// Initialize the global logger
	config := logger.Config{
		Level:        viper.GetString("log.level"),
		Development:  viper.GetBool("debug"),
		FilePath:     viper.GetString("log.path"),
		JSON:         viper.GetBool("log.json"),
		DisableColor: viper.GetBool("log.disable_color"),
	}

+	// Validate log level
+	validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
+	if _, valid := validLevels[config.Level]; !valid && config.Level != "" {
+		return fmt.Errorf("invalid log level: %s", config.Level)
+	}
+
+	// Validate log path if provided
+	if config.FilePath != "" {
+		// Check if directory exists
+		dir := filepath.Dir(config.FilePath)
+		if _, err := os.Stat(dir); os.IsNotExist(err) {
+			if err := os.MkdirAll(dir, 0755); err != nil {
+				return fmt.Errorf("failed to create log directory: %w", err)
+			}
+		}
+	}

	if err := logger.InitGlobal(config); err != nil {
		return fmt.Errorf("error initializing logger: %w", err)
	}

	// Log initialization success
	logger.Info("BirdNET-Go initialized",
		"version", viper.GetString("version"),
		"build_date", viper.GetString("build_date"))

	return nil
}
internal/diskmanager/policy_usage.go (6)

21-25: Consider centralizing logger initialization.
Repeating the “if diskLogger == nil { InitLogger() }” pattern within each function can be difficult to maintain and prone to forgetting in certain paths. Consider centralizing logger initialization in one place (e.g., during package init or a dedicated initializer) to keep the code DRY.


52-55: Ensure thorough disk usage context.
This debug statement logs usage and threshold. It might also be useful to log the difference or percentage above threshold when debugging.


85-89: Duplicate initialization logic.
Just like lines 21-25, consider extracting the logger initialization to a shared helper or ensuring it’s invoked once in a top-level function. Doing so will reduce repeated checks.


148-148: Error log on file removal failure.
Logging path and error is appropriate. Consider capturing file permissions or ownership if repeated failures are common.


172-174: Summarizes usage retention outcome.
Logging total files deleted and space freed is informative. Consider adding final disk usage for completeness.


181-185: Logger initialization repeated again.
Similar to lines 21-25 and 85-89, consider a single initialization approach to avoid code duplication.

internal/datastore/sqlite.go (1)

67-67: Consistent backup filename pattern.
Switching from underscore to a dot in the extension is purely cosmetic but can help visually distinguish backups.

internal/logger/logger.go (2)

69-72: Consider including milliseconds for precise timing

While the simplified time format is readable, consider including milliseconds for debugging time-sensitive operations.

-func simpleDateTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
-	enc.AppendString(t.Format("2006-01-02T15:04:05"))
+func simpleDateTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+	enc.AppendString(t.Format("2006-01-02T15:04:05.000"))
}

99-241: Consider decomposing the NewLogger function

While the implementation is thorough, this function is quite complex. Consider breaking it down into helper methods for each logger type to improve maintainability.

For example:

func createDevelopmentFileLogger(config Config, rc RotationConfig, level zapcore.Level, opts []zap.Option) (*zap.Logger, error) {
    // Implement case 1 logic here
}

func createProductionFileLogger(config Config, rc RotationConfig, level zapcore.Level, opts []zap.Option) (*zap.Logger, error) {
    // Implement case 2 logic here
}

func createConsoleLogger(config Config, level zapcore.Level, opts []zap.Option) (*zap.Logger, error) {
    // Implement case 3 logic here
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 874beb0 and 16be57b.

📒 Files selected for processing (13)
  • cmd/root.go (2 hunks)
  • internal/analysis/realtime.go (5 hunks)
  • internal/datastore/interfaces.go (15 hunks)
  • internal/datastore/manage.go (1 hunks)
  • internal/datastore/mysql.go (1 hunks)
  • internal/datastore/sqlite.go (1 hunks)
  • internal/diskmanager/file_utils.go (6 hunks)
  • internal/diskmanager/policy_age.go (6 hunks)
  • internal/diskmanager/policy_usage.go (9 hunks)
  • internal/httpcontroller/handlers/dashboard.go (7 hunks)
  • internal/httpcontroller/handlers/detections.go (27 hunks)
  • internal/httpcontroller/handlers/utils.go (0 hunks)
  • internal/logger/logger.go (1 hunks)
💤 Files with no reviewable changes (1)
  • internal/httpcontroller/handlers/utils.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (86)
cmd/root.go (4)

19-19: Confirm this new import is correctly used.

The new logger package import is appropriate for the implementation of the structured logging system as described in the PR objectives.


76-83: Logger configuration looks well structured.

The logger configuration is properly populated from viper settings, with appropriate fields for level, development mode, file path, JSON formatting, and color settings. This aligns with the PR objective of implementing a unified logging system with configuration options.


85-87: Good error handling for logger initialization.

The error from logger initialization is properly wrapped with a descriptive message, making troubleshooting easier.


90-93: Excellent use of structured logging.

This is a good example of structured logging with field-based contextual information (version and build date). This demonstrates the context-based logging capability mentioned in the PR objectives.

internal/diskmanager/policy_age.go (2)

15-18: Good defensive programming.

Adding logger initialization check ensures logging works even if initialization was missed elsewhere.


29-29: Well-structured logging with context.

The transition from standard logging to structured logging with key-value pairs greatly improves log quality and searchability. The consistent use of appropriate log levels (Error, Debug, Info) based on the context is excellent.

Also applies to: 34-36, 63-63, 69-69, 79-83, 89-89, 102-102, 111-111

internal/analysis/realtime.go (2)

72-74: Good approach to component-specific logging.

Setting a named logger for the datastore component using the global logger is a good practice for organizing logs.


274-275: Good practice for component-specific logging.

Creating a named logger for each functional component helps with log organization and filtering.

internal/diskmanager/file_utils.go (3)

36-42: Good design for module-specific logger.

Creating a package-level logger variable and an initialization function provides a clean way to handle logging throughout the package.


46-49: Consistent defensive initialization.

The consistent logger initialization checks throughout the package ensure logging works correctly regardless of initialization order.

Also applies to: 84-87, 183-186


98-98: Well-structured logging with context.

The structured logs with key-value pairs provide more context than the previous simple log messages.

Also applies to: 207-207

internal/httpcontroller/handlers/dashboard.go (5)

26-27: Good component-specific logger design.

Creating a dedicated method for obtaining the dashboard logger improves code organization and reusability.

Also applies to: 256-262


33-36: Comprehensive debug logging with performance metrics.

The debug logging includes valuable performance metrics (duration, counts) and is properly guarded with debug flag checks. The yield point with logging during batch processing is an excellent practice for long-running operations.

Also applies to: 54-58, 75-81, 128-134, 164-168, 212-218, 222-227, 248-252


42-46: Well-structured error logging.

Error logging consistently includes contextual information (date, species, etc.) which will be valuable for troubleshooting. The nil check for the logger is a good defensive practice.

Also applies to: 66-71, 86-91, 106-110, 117-121, 156-160, 193-198, 241-244


154-162: Improved error handling for template rendering.

The addition of error handling and logging for template rendering is a good improvement that will help identify UI issues.


210-218: Excellent concurrency consideration.

Adding a yield point during intensive processing prevents CPU blocking and improves responsiveness. The debug logging for batch progress is helpful for monitoring long-running operations.

internal/diskmanager/policy_usage.go (10)

39-41: Looks good for debug-level logging.
Logging base directory and threshold is helpful for troubleshooting.


76-78: Efficient zero-work path recognized.
This debug-level message helps confirm no cleanup is needed. This is good practice for a quick exit log.


98-98: Informational log on quit signal.
This effectively notifies operators that the cleanup is being halted. No issues found.


104-104: Good clarity on locked file skip.
This debug message succinctly explains why a file wasn’t removed.


125-128: Debug-level species count logging.
Logging species clip counts is helpful for diagnosing edge cases. Ensure it won’t cause excessive log volume in production if debug is accidentally enabled.


133-137: Meaningful skip message.
Indicates the reason a file is not being deleted. This is helpful for explaining incomplete cleanup runs.


142-142: Clear “Deleting file” debug message.
Short and straightforward. Ensures clarity when reviewing logs.


160-163: Good final log of file deletion.
This helps confirm that the operation completed, including updated clip counts.


187-187: Useful debug describing sort priority.
Confirms the sorting approach, which helps in diagnosing unexpected cleanup order.


228-228: “Files sorted” debug message.
Helpful for confirming sort completion. No issues noted.

internal/datastore/mysql.go (10)

31-32: Logger retrieval in Open method.
Having a dedicated logger for MySQL operations is beneficial for scoping log output and diagnosing DB-specific issues.


40-40: GORM logger creation.
Creating a GORM-compatible logger ensures consistent, structured log output. No issues seen.


45-53: Detailed connection error logs.
Providing host, port, database, and the error is helpful for troubleshooting. This approach improves observability.


58-58: Return statement includes auto migration.
Performing auto migration here is consistent with the rest of the code. Consider verifying performance impact in production if migrations are large.


63-65: Acquiring logger in Close.
Same pattern as in Open. This ensures uniform log messages for MySQL connections.


68-72: Connection uninitialized check.
Appropriate error handling with descriptive message. Good approach to fail fast.


77-81: Logging DB object retrieval failure.
Capturing the error in both custom logger and fallback standard log is a good fallback pattern.


87-91: Graceful closure failure logs.
Same pattern for error logging. Consistent approach across the file.


95-97: Debug logging on successful close.
Good practice to confirm resource cleanup. No concerns here.


102-106: UpdateNote logic switched to uint.
Changing note ID to uint can reduce invalid ID conversions at runtime. Proper error wrapping helps debugging.

internal/datastore/interfaces.go (14)

13-15: Introducing custom logger import.
This replaces the standard log package with a structured logger. The GORM logger alias also clarifies usage.


65-65: New SetLogger method in interface.
This addition allows for dynamic logger injection, increasing flexibility for testing and production.


70-71: DataStore now holds a logger.
Embedding a structured logger in the store fosters consistent logging across DB operations.


74-77: Implementation of SetLogger.
Straightforward setter method. No issues found.


79-86: getDbLogger for granular DB operations.
Naming sub-loggers after operations (e.g., “db.save”, “db.lock”) fosters clarity. Good approach.


110-112: Transaction-specific logger retrieval.
Defining a distinct logger name for each DB transaction step helps trace issues more precisely.


168-181: Warn logs for “database locked” with retry details.
Providing transaction ID, attempt count, and delay is excellent for diagnosing concurrency issues.


191-199: Info logs for successful retry.
This indicates how many attempts were needed. Great for measuring lock contention frequency.


207-212: Error logging after all attempts fail.
Consolidated context (tx_id, error, max_attempts) is beneficial for triaging fatal DB lock scenarios.


439-444: Structured log for retrieval performance.
Including the retrieval time in milliseconds can help measure DB performance. Good addition.


646-647: Silencing GORM logger for sensitive query.
Switching the gormlogger to Silent is sometimes necessary for privacy or noise reduction. This approach is valid.


982-982: Disabling GORM logs for GetImageCache.
Reduces noise for repeated image cache lookups. Keep an eye on debug needs if diagnosing image cache issues.


999-999: Disabling GORM logs for SaveImageCache.
Similar reasoning as above. Minimizing log spam can benefit performance.


1013-1013: Disabling GORM logs for GetAllImageCaches.
Consistent with other queries. Fine approach for read-heavy or less critical queries.

internal/datastore/sqlite.go (21)

59-60: Backup function extracted from method scope.
Making createBackup a standalone function is useful if it’s reused or tested independently.


65-65: Comment clarifies backup filename creation.
The timestamp-based naming is good for versioning.


72-72: Effective error context.
This message clarifies that opening the source file failed, simplifying analysis.


77-77: Destination file creation message.
No issues, short and descriptive.


83-86: Copying file content with error checking.
Properly returning the underlying error ensures any backup failures are surfaced.


93-97: Open method logic restructured.
Performing SQLite config validation early is a clear improvement. Good for ensuring the DB is set up properly.


98-99: Component-specific logger.
This mimics patterns in mysql.go. Maintaining consistent approach is beneficial for debugging differences.


101-103: Retrieving DB path from config.
Falling back to defaults if empty is user-friendly. Good practice.


116-116: Use of default config path.
Selecting the first config directory if the path is empty is sensible. However, handle edge cases if multiple directories exist or if the user wants a different one.


120-128: Ensure parent directory creation.
Creating directories if they don’t exist is helpful for first-run initialization, with thorough error logging.


131-138: Write permission checks.
Proactively discovering permission errors prevents confusing runtime issues downstream. Good approach.


141-160: Disk space validation.
Warnings on partial failure (missing space measurement) is a good fallback strategy. The user is at least informed.


163-175: Conditional backup creation.
Creating a backup only when the DB is over 1MB helps reduce overhead. Good trade-off.


179-179: New GORM logger creation.
Using a custom logger aligns with the overall unified logging system in this PR.


182-183: Attaching logger to gorm.Config.
Ensures consistency for every query.


187-197: SQLite database opening with WAL & timeout.
Enables improved concurrency. Logging errors with “path” and “error” is good thoroughness.


199-214: Setting connection pool parameters.
Including error logs if DB() retrieval fails is consistent. No issues found.


215-216: Perform auto-migration post-initialization.
Keeps the pattern consistent with the MySQL store.


220-230: Close method logger pattern.
Matching other DB store closes. The early return if DB is nil is a good safeguard.


232-240: Retrieving generic DB object and logging failures.
Consistent approach to error reporting. Clear context is provided in log messages.


243-255: Final close with debug on success.
Mirrors the MySQL approach. Ensures the user is informed about successful or failed closure.

internal/datastore/manage.go (4)

9-11: Nice imports organization

Good job adding the custom logger import and using a named import for the GORM logger to avoid naming conflicts.


14-28: Well-documented logger integration

Good approach to maintain backward compatibility while allowing for future custom logger integration. The comments clearly explain the current limitations and rationale.

Consider implementing the GORM logger adapter in the future to fully leverage structured logging for database operations.


30-53: Clean implementation of logger fallback

The function properly uses the custom logger when available and gracefully falls back to standard logging. This ensures backward compatibility while enabling structured logging.


55-62: Well-implemented component-specific logger

Good pattern for creating named loggers that follow a hierarchical structure. This will help with log filtering and organization.

internal/logger/logger.go (5)

2-30: Excellent package documentation

The documentation clearly explains the key features, usage patterns, and configuration options. The examples are particularly helpful for new developers.


44-49: Good logger structure design

Wrapping both the standard Zap logger and sugared logger gives flexibility while maintaining a clean API.


51-67: Well-configured encoder settings

The encoder configuration provides a good balance between readability and information density.


75-77: Review potential security concern with header logging

There was a previous code scanning alert about sensitive data from HTTP headers being logged. Ensure the Debug method doesn't inadvertently log sensitive information from headers.


243-270: Good utility methods for logger contextualization

The Named, With, Sync, and GetZapLogger methods provide a clean API for logger customization and management.

internal/httpcontroller/handlers/detections.go (6)

166-170: Good structured logging implementation

The error logging now includes context about what operation failed along with the error details, making debugging easier.


178-186: Well-implemented component-specific logger

Using a dedicated logger for each component helps with log organization and filtering.


255-271: Properly deprecated legacy logging method

Good approach to maintain backward compatibility while encouraging migration to structured logging.


675-712: Excellent retry logic with structured logging

The retry mechanism is well implemented with proper logging of attempts, delays, and outcomes.


714-863: Good separation of concerns for database operations

Breaking down the review and lock operations into separate methods improves readability and maintainability.


1021-1027: Useful debug logging for lock operations

The structured logging includes all relevant context for debugging lock-related issues.

tphakala added 4 commits March 2, 2025 16:38
- Remove hardcoded database backup mechanism in Open() method
- Simplify database initialization process
- Align with ongoing logging and error handling refactoring efforts

This change removes redundant backup logic, potentially to be replaced with a more robust backup strategy in future implementations.
- Extract encoder, level, and option configuration into separate helper functions
- Improve code modularity in logger initialization and configuration
- Add more flexible and reusable methods for creating loggers
- Standardize log level, encoder, and zap option generation
- Simplify logger creation process across different use cases
- Ensure consistent handling of caller information and development mode

This refactoring improves the logger's flexibility, readability, and makes configuration more modular and maintainable.
…ge fetching

- Replace standard logging with structured, component-specific logging in image provider
- Use global logger with named components in Wikipedia image fetching
- Improve debug logging with more context and request tracing
- Enhance error handling and logging in thumbnail and author info retrieval
- Remove redundant log statements and simplify logging configuration
- Add more informative and consistent log messages for image cache and fetching operations

This refactoring improves logging consistency, error traceability, and observability in image-related operations.
- Remove redundant startup log message for audio sources
- Rename clip cleanup monitor logger to a more concise "cleanup"
- Maintain consistent logging approach across realtime analysis components
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
internal/httpcontroller/server_logger.go (1)

28-47: Consider simplifying conditional message formatting.

The Debug method correctly handles development mode logging with fallback to standard logging, but the message formatting logic could be more concise.

func (s *Server) Debug(format string, v ...interface{}) {
    if s.isDevMode() {
        // In debug mode, always use the logger if available
        if s.Logger != nil {
-           message := format
-           if len(v) > 0 {
-               message = fmt.Sprintf(format, v...)
-           }
-           s.Logger.Debug(message)
+           if len(v) > 0 {
+               s.Logger.Debug(fmt.Sprintf(format, v...))
+           } else {
+               s.Logger.Debug(format)
+           }
        } else {
            // Fall back to standard log if logger isn't initialized yet
            if len(v) == 0 {
                log.Print(format)
            } else {
                log.Printf(format, v...)
            }
        }
    }
}
internal/logger/logger.go (2)

75-174: Comprehensive logger initialization with good error handling

The NewLogger function handles multiple configuration scenarios well and provides appropriate error handling. The implementation intelligently branches based on configuration to create the right logger for each use case.

Consider adding a helper method to allow for dynamic log level changes at runtime, which would be useful for debugging production issues without restarting the application.


176-192: Effective context enrichment methods

The Named and With methods provide good support for creating context-rich logs, which is essential for traceability in complex applications.

Consider adding a WithContext method that extracts logging fields from a context.Context object, which would better integrate with Go's context pattern for request-scoped logging.

internal/imageprovider/imageprovider.go (1)

41-41: Consider using an interface for the logger field.
Exposing the logger as *logger.Logger is fine, but using an interface can make testing easier.

internal/logger/config.go (2)

27-37: Consider providing a more specific default log level.

In the DefaultConfig, the Level field is set to an empty string. While this gets interpreted properly in GetLogLevel, it would be more explicit to set it to "debug" directly for development environments.

return Config{
-	Level:         "",
+	Level:         "debug",
	JSON:          false,
	Development:   true,
	FilePath:      "",
	DisableColor:  false,
	DisableCaller: true,
}

91-94: Consider adding timezone information in logs.

The simpleDateTimeEncoder formats time without timezone information, which could make troubleshooting across different environments challenging. Consider adding timezone offset or using UTC explicitly.

func simpleDateTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
-	enc.AppendString(t.Format("2006-01-02T15:04:05"))
+	enc.AppendString(t.Format("2006-01-02T15:04:05Z07:00"))
}
internal/imageprovider/wikipedia.go (1)

287-298: Redundant logging statements could be consolidated.

There are multiple consecutive debug statements logging similar information about the thumbnail. Consider consolidating these into a single, more comprehensive log entry to reduce log verbosity.

if l.debug && l.logger != nil {
	l.logger.Debug("Successfully retrieved thumbnail",
		"request_id", reqID,
-		"url", url,
-		"file", fileName)
-	l.logger.Debug("Successfully retrieved thumbnail URL",
-		"request_id", reqID,
		"url", thumbnailURL,
		"file", fileName)
-	l.logger.Debug("Thumbnail source file",
-		"request_id", reqID,
-		"file", fileName)
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16be57b and ed77f7e.

📒 Files selected for processing (12)
  • cmd/root.go (2 hunks)
  • internal/analysis/realtime.go (5 hunks)
  • internal/datastore/sqlite.go (1 hunks)
  • internal/httpcontroller/server.go (5 hunks)
  • internal/httpcontroller/server_logger.go (1 hunks)
  • internal/httpcontroller/template_renderers.go (8 hunks)
  • internal/imageprovider/imageprovider.go (18 hunks)
  • internal/imageprovider/wikipedia.go (6 hunks)
  • internal/logger/config.go (1 hunks)
  • internal/logger/global.go (1 hunks)
  • internal/logger/logger.go (1 hunks)
  • internal/logger/testing.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • cmd/root.go
  • internal/logger/testing.go
  • internal/httpcontroller/server.go
  • internal/analysis/realtime.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (74)
internal/httpcontroller/server_logger.go (5)

14-26: Well-designed adapter pattern for Echo logging integration.

The echoLogAdapter provides a clean solution to bridge the custom logger with Echo's logging system by implementing the io.Writer interface. The implementation correctly handles whitespace trimming and empty message filtering.


49-78: Robust logger initialization with proper error handling.

The initLogger method correctly handles configuration, development mode detection, and integration with Echo's logging system. Using a named logger from the global instance promotes consistency across the application.


80-109: Well-structured request logger setup with context enrichment.

The first part of setupRequestLogger properly configures the middleware and captures important request context like request IDs and path parameters for correlation.


110-125: Smart log level selection based on HTTP status codes.

The status-based logging level selection is a well-thought-out approach that ensures appropriate visibility for different types of responses. Using error logs for 5xx, warn for 4xx, and debug for 3xx (in dev mode) is a good practice.


126-173: Comprehensive structured logging with conditional field inclusion.

The log entry construction is thorough and efficient, including:

  • Essential fields for all requests
  • Conditional inclusion of optional fields
  • Special handling for error cases
  • Development-mode-specific detailed logging

This approach balances verbosity with performance by only including necessary context.

internal/logger/logger.go (5)

1-41: Well-structured package documentation with clear examples

The package documentation provides a comprehensive overview of the logger's capabilities and includes practical usage examples. This is excellent for developer onboarding and understanding the package's features.


43-48: Good encapsulation of zap loggers

The Logger struct effectively wraps both the standard zap.Logger and the SugaredLogger, providing a clean interface while maintaining access to zap's powerful features.


50-53: Potential security concern with Debug logging

Be cautious with debug logging as it might inadvertently log sensitive information. Consider implementing sanitization for sensitive fields when in debug mode.

This comment addresses the previously identified security issue where "Sensitive data returned by HTTP request headers flows to a logging call."


50-73: Consistent logging methods with structured logging support

The implementation of logging methods (Debug, Info, Warn, Error, Fatal) follows a consistent pattern and properly utilizes zap's structured logging capabilities.


194-203: Important utility methods for advanced usage

The Sync method is crucial for preventing log loss at application shutdown, and providing access to the underlying zap.Logger offers flexibility for advanced use cases.

internal/imageprovider/imageprovider.go (22)

6-6: No issues with keeping fmt import.
It’s okay to retain fmt for potential string formatting or error wrapping.


13-13: Importing the custom logger.
This import is essential for structured logging.


72-74: Structured debug logging for refresh start.
Keeps logs cleaner and only logs in debug mode.


87-88: Same debug-check pattern.


107-109: Same debug-check pattern.


114-116: Same debug-check pattern.


124-127: Same debug-check pattern.


134-135: Same debug-check pattern.


140-142: Same debug-check pattern.


166-168: Same debug-check pattern.


173-176: Same debug-check pattern.


183-187: Same debug-check pattern.


214-216: Naming the logger for clarity.
Good approach to differentiate loggers by component name.


223-223: Assigning the component logger.
Ensures logs include “imageprovider.cache”.


244-246: Same debug-check pattern.


252-256: Same debug-check pattern.


279-281: Same debug-check pattern.


297-300: Same debug-check pattern.


309-310: Same debug-check pattern.


416-418: Same debug-check pattern.


425-428: Same debug-check pattern.


473-475: Same debug-check pattern.

internal/datastore/sqlite.go (3)

59-90: Backup logic simplified successfully.
Creating a backup only if the file exists is a sensible check. Using a timestamp with a dot separator is fine. Error messages are clearer now.


93-201: Open method refactor.

  1. Good structured logging with sqliteLogger.
  2. Proper checks for path, directory creation, permissions, and disk space.
  3. The fallback to standard log when sqliteLogger is nil is consistent but consider guaranteeing the logger is never nil.

204-241: Close method improvements.

  1. Checks if store.DB is nil to prevent panic.
  2. Logs errors if closing fails.
  3. Debug log for successful close is a nice detail.
internal/httpcontroller/template_renderers.go (15)

13-13: Logger import.
Facilitates new structured logging.


36-37: New logger and debug fields in struct.
Enables structured debug logs. Great addition.


57-64: Error logging on template execution failure.
Good structured context with template name and error.


72-79: Logging write errors.
Again, structured approach is helpful.


87-90: Setting up a named logger.
Enhances traceability for template-related logs.


125-128: Component-specific logger for rendering.
Keeps logs organized by functionality.


134-139: Invalid data type error.
Returning typed error is good. Lint warnings about direct fmt.Errorf(errMsg) can appear; see prior notes if that rule is still enforced.


156-161: No route found scenario.
Using structured logging clarifies the missing route.


179-183: Successful render in debug mode.
Nice detail to confirm in development.


192-195: Named logger for settings.
Continues the theme of specialized loggers.


202-220: CSRF token checks with structured logging.
Warnings for missing token are helpful. Good environment-based decision for debug logs.


233-239: Species config logging in debug mode.
Useful for diagnosing data passed to the template.


247-256: Detailed logging on rendering errors.
Printing data keys on failure can be quite helpful for debugging.


260-265: Debug message for successful render.
Keeps track of successful template usage.


270-278: Helper function for data keys.
Aids logging clarity by listing relevant fields.

internal/logger/global.go (13)

1-2: File introduction and package declaration.
Organizes global logger in a dedicated package.


4-8: Imports and sync usage.
synchronize global logger initialization is well handled.


10-15: Global variables for default logger.
The sync.Once approach prevents race conditions.


17-34: InitGlobal method.

  1. Enforces DisableCaller.
  2. Prints a debug message about original config.
  3. Retains first successful init only.

36-56: GetGlobal method.

  1. Provides lazy initialization.
  2. Logs config on creation.
  3. Exits if initialization absolutely fails.

58-61: Global Debug function.
Centralizes debug logging. No concerns.


63-66: Global Info function.
No issues. Using the global logger is convenient.


68-71: Global Warn function.
Properly defers to the underlying Warn.


73-76: Global Error function.
Centralized approach, consistent with other levels.


78-81: Global Fatal function.
Exits after logging. Ensure usage is deliberate.


83-86: Named logger derived from global.
Helps maintain separate contexts for different subsystems.


88-91: Global With for field context.
Chains fields effectively.


93-96: Sync method.
Clean flush approach for the global logger.

internal/logger/config.go (5)

11-25: Good structure for the Config type with clear documentation.

The Config struct is well-designed with comprehensive field documentation and appropriate JSON tags. The fields cover essential logging configuration needs including level, format options, and output destinations.


51-61: Well-designed RotationConfig with appropriate defaults.

The RotationConfig struct properly encapsulates all necessary parameters for log rotation with clear documentation and JSON tags.


112-122: Good implementation of log level determination.

The GetLogLevel function intelligently handles default levels based on environment and allows for explicit overrides through configuration, providing good flexibility.


124-142: Good handling of console vs file output formatting.

The GetEncoderConfig function properly distinguishes between console and file outputs, applying color coding only when appropriate for the output medium and user preferences.


152-163: Robust level parsing with graceful fallback.

The getZapLevel function handles empty values and parsing errors gracefully by providing sensible defaults, which prevents configuration issues from causing application failures.

internal/imageprovider/wikipedia.go (6)

16-16: Good integration of the new logger package.

Adding the logger package import is appropriate for the structured logging implementation.


27-27: Well-implemented logger integration in provider struct.

The addition of the logger field to the struct and initialization using a named component logger follows good logging practices for component isolation.

Also applies to: 47-49, 56-57


168-169: Well-implemented structured logging with request ID tracing.

The implementation of structured logging with request IDs provides excellent traceability across operation steps. The conditional checks for both debug mode and logger existence prevent unnecessary processing.

Also applies to: 65-69, 83-88, 100-104, 108-112, 116-120, 124-133, 140-146, 150-159


162-163: Improved error message clarity.

Changing from "no pages found for request" to "no pages found in response" provides a more precise description of the actual error condition.


333-340: Improved error handling for empty imageInfo array.

The code now properly checks if the imageInfo array is empty before attempting to access its elements, making the error handling more robust.


365-376: Good implementation of HTML parsing with fallback.

The code properly handles extracting artist information from HTML content with a fallback to plain text when HTML parsing fails, making the system more resilient.

tphakala added 2 commits March 2, 2025 18:16
…cation components

- Add named root logger in application initialization
- Implement hierarchical logger propagation in HTTP server and API components
- Update logger initialization to support explicit parent logger injection
- Improve logger naming consistency across server, API, and other components
- Remove redundant global logger debug print statements
- Enhance logger configuration with more flexible initialization methods

This refactoring standardizes logging approach, improves component-specific logging, and provides more consistent and informative log messages throughout the application.
- Remove global logger dependency in root command and realtime analysis
- Implement hierarchical logger propagation in BirdNET core initialization
- Add logger injection support for BirdNET interpreter and related components
- Update model reload and debug methods to use structured logging
- Improve logging configuration with more flexible initialization methods
- Remove redundant debug print statements and replace with structured logging

This refactoring standardizes logging approach, improves component-specific logging, and provides more consistent and informative log messages in BirdNET core functionality.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (16)
internal/logger/global.go (4)

10-15: Consider the implications of introducing a global logger.

Relying on global state can make tests more complex to isolate and can introduce hidden dependencies between modules. You may want to allow an option for injecting a custom logger instance to keep the code more modular and maintainable.


36-47: Avoid abrupt termination with os.Exit(1).

Libraries or shared modules typically avoid calling os.Exit(1), as it terminates the entire process prematurely. Consider returning an error or using fallbacks (e.g., a minimal logger) to allow applications using this library to decide how to handle failures.


52-75: Promote testability by minimizing global logger calls.

Accessing the global logger directly in multiple helper methods can complicate testing, especially when stubbing or capturing logs. Consider injecting dependencies (e.g., a logger interface) instead of relying on package-level logging methods for improved reusability and easier test isolation.


87-90: Clarify how callers should handle the returned error.

The Sync() function returns an error code, but it is unclear how or where this error should be processed. If you expect callers to handle or log the error, consider adding usage instructions or an example in the package documentation.

cmd/benchmark/benchmark.go (1)

88-92: Consider adding log statements for benchmark progress

The logger has been properly integrated, but there's an opportunity to enhance the benchmarking process by adding structured log entries for key events such as start, completion, and performance metrics. This would complement the existing console output with structured logs for automated analysis.

 func runInferenceBenchmark(settings *conf.Settings, results *benchmarkResults) error {
 	// Initialize a logger for the benchmark
 	benchmarkLogger := logger.Named("benchmark")
+	benchmarkLogger.Info("Starting benchmark run", 
+		"useXNNPACK", settings.BirdNET.UseXNNPACK)
 
 	// Initialize BirdNET
 	bn, err := birdnet.NewBirdNET(settings, benchmarkLogger)

And near line 132, consider adding:

 	results.totalInferences = totalInferences
 	results.avgTime = totalDuration / time.Duration(totalInferences)
 	results.inferencesPerSecond = float64(totalInferences) / duration.Seconds()
+	
+	benchmarkLogger.Info("Benchmark completed",
+		"totalInferences", totalInferences,
+		"avgTimeMs", results.avgTime.Milliseconds(),
+		"inferencesPerSecond", results.inferencesPerSecond,
+		"useXNNPACK", settings.BirdNET.UseXNNPACK)
internal/api/v2/streams.go (1)

168-168: Improved error logging in writePump using the new logf method

All error logging in the writePump method now uses the new centralized logf method, which provides consistent error reporting across the client methods.

Consider adding more structured context to these error logs by using key-value pairs to provide additional debugging information when errors occur:

-client.logf("Failed to set write deadline: %v", err)
+client.logf("Failed to set write deadline: %v", err)

In a future update, you might consider converting this to fully structured logging with a logger that supports key-value pairs:

client.logger.Error("Failed to set write deadline", "error", err)

Also applies to: 175-175, 182-182, 187-187, 195-195, 201-201, 207-207, 212-212, 217-217

internal/logger/README.md (2)

669-671: Missing language specification in fenced code block

The fenced code block showing the nil pointer panic doesn't specify a language.

-```
-panic: runtime error: invalid memory address or nil pointer dereference
-```
+```text
+panic: runtime error: invalid memory address or nil pointer dereference
+```
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

669-669: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


187-187: Loose punctuation mark in list item

There's an extra space after the colon in the list of test helpers.

-- `CreateTestCore`: Creates a logger core that writes to a test buffer
+- `CreateTestCore`: Creates a logger core that writes to a test buffer
🧰 Tools
🪛 LanguageTool

[uncategorized] ~187-~187: Loose punctuation mark.
Context: ...nctions for testing: - CreateTestCore: Creates a logger core that writes to a ...

(UNLIKELY_OPENING_PUNCTUATION)

internal/api/v2/api.go (1)

229-251: Consistent logging helpers with appropriate levels

These helper methods provide a clean interface for formatted logging at different levels, maintaining consistency across the codebase.

However, there's some code duplication between Debug and LogfDebug methods where both check the debug setting. Consider refactoring to avoid this duplication.

// LogfDebug is a helper for logging debug messages with Printf-style formatting using the structured logger
func (c *Controller) LogfDebug(format string, v ...interface{}) {
-	if !c.Settings.WebServer.Debug {
-		return
-	}
-
-	c.logger.Debug(fmt.Sprintf(format, v...))
+	if c.Settings.WebServer.Debug {
+		c.logger.Debug(fmt.Sprintf(format, v...))
+	}
}
internal/analysis/realtime.go (7)

32-34: Document the function more thoroughly.
While these lines declare RealtimeAnalysis and an err variable, consider documenting the return behavior and any potential side effects in more detail, particularly given the complexity of the routine.


166-169: Processor logger.
Currently, you’ve commented out proc.SetLogger(...). If the processor eventually supports logger injection, consider finalizing it to further unify logging.


180-185: Buffer manager logger injection.
Although it’s commented out, consistent logger injection across all major components (e.g., the buffer manager) will help unify logs. Consider implementing it once the buffer manager supports it.


257-262: Clip cleanup monitor with injected logger.
Injecting the logger fosters more granular logs for disk cleanup. Also note that diskmanager.InitLogger() is still a separate step; performing direct injection is usually preferable over static initialization.


270-302: New clipCleanupMonitor function looks coherent.
The function polls at 5-minute intervals, handles “age” and “usage” retention policies, logs errors, and exits gracefully upon a quit signal.

  • Consider verifying concurrency correctness, ensuring no race conditions with the dataStore or disk usage checks.
  • The repeated checks for kill signal within the cleanup calls are appropriate to handle abrupt shutdown.

326-346: Telemetry endpoint logger injection.
In line with the rest of your design, you’ve included the telemetry endpoint in the hierarchical logging structure. The disabled block for SetLogger(logger) is fine, but consider implementing once that component is ready.


372-435: initBirdImageCache is well-structured.
Your approach:

  1. Create the cache.
  2. Retrieve species from DB.
  3. Launch goroutines to fetch images concurrently.

Suggestions:

  • Make sure concurrency is thoroughly tested, especially for partial failures or repeated calls.
  • Consider a wait before repeated attempts, or caching results more aggressively, if startup times become an issue.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed77f7e and 212c8f3.

📒 Files selected for processing (17)
  • cmd/benchmark/benchmark.go (2 hunks)
  • cmd/rangefilter/print.go (2 hunks)
  • cmd/realtime/realtime.go (1 hunks)
  • cmd/root.go (1 hunks)
  • go.mod (3 hunks)
  • internal/analysis/birdnet_init.go (2 hunks)
  • internal/analysis/realtime.go (8 hunks)
  • internal/api/v2/api.go (7 hunks)
  • internal/api/v2/integration.go (0 hunks)
  • internal/api/v2/integrations.go (3 hunks)
  • internal/api/v2/streams.go (9 hunks)
  • internal/api/v2/weather.go (4 hunks)
  • internal/birdnet/birdnet.go (12 hunks)
  • internal/httpcontroller/server.go (6 hunks)
  • internal/httpcontroller/server_logger.go (1 hunks)
  • internal/logger/README.md (1 hunks)
  • internal/logger/global.go (1 hunks)
💤 Files with no reviewable changes (1)
  • internal/api/v2/integration.go
✅ Files skipped from review due to trivial changes (1)
  • cmd/root.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/httpcontroller/server_logger.go
🧰 Additional context used
🪛 LanguageTool
internal/logger/README.md

[uncategorized] ~187-~187: Loose punctuation mark.
Context: ...nctions for testing: - CreateTestCore: Creates a logger core that writes to a ...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 markdownlint-cli2 (0.17.2)
internal/logger/README.md

669-669: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (63)
internal/logger/global.go (1)

20-31: Good use of sync.Once for safe concurrency.

Your initialization logic efficiently prevents race conditions and unnecessary re-initialization, ensuring a thread-safe setup.

internal/analysis/birdnet_init.go (1)

18-20: Well-structured logger implementation

The integration of the structured logger with a clear hierarchical naming pattern (birdnet-go.analyzer) follows good practices for organized logging. Passing the logger to NewBirdNET allows for component-specific logging.

go.mod (3)

32-32: Good choice of logging library

Adding Zap is appropriate for implementing structured logging as specified in the PR objectives. Zap is a high-performance, structured logging library that will support the desired JSON and human-readable formats.


37-37: Appropriate log rotation implementation

The addition of lumberjack for log rotation is a good choice that complements the Zap logging system, enabling proper log file management as mentioned in the PR objectives.


97-97: Library version update looks good

Updating multierr to v1.11.0 aligns well with the Zap dependency, as they are from the same ecosystem.

cmd/rangefilter/print.go (1)

19-22: Consistent logger naming pattern

The logger naming follows the same pattern as seen in other files, maintaining consistency across the codebase. The placement right before using it in the NewBirdNET function creates a clear flow.

internal/api/v2/streams.go (5)

56-63: Good implementation of the logf method with proper fallback

The helper method is well-implemented with a proper fallback to the standard logger when the client logger is nil, providing consistent logging behavior.


80-80: Using the new controller logging method correctly

This line correctly uses the new controller's structured logging method for error reporting.


91-91: Consistent logger initialization across client creation methods

Both client creation blocks now consistently initialize the logger field with the default logger.

Also applies to: 121-121


100-100: Passing logger to readPump for consistency

The default logger is now consistently passed to the readPump method, ensuring that the client always has a logger available.

Also applies to: 129-129


238-238: Standardized error logging in readPump

The readPump method now uses the client's logf method for logging errors related to setting read deadlines, providing consistent error reporting.

Also applies to: 247-247

internal/api/v2/weather.go (4)

5-5: Import of fmt package for string formatting

The fmt package is now imported to properly support the string formatting needed in the new structured logging calls.


137-140: Improved structured logging for future date warning

The logging calls have been updated to use structured logging with key-value pairs, which is more consistent with modern logging practices and makes logs easier to parse and analyze.


155-158: Consistent structured logging for missing data warning

This logging statement uses the same structured format as other logs, improving consistency throughout the application.


446-449: Enhanced error logging with structured key-value pairs

This logging statement now provides clear context with key-value pairs for the date, error, and endpoint, making it easier to track and debug issues.

cmd/realtime/realtime.go (2)

14-14: Simplified function documentation

The comment has been updated to be more concise while still accurately describing the function's purpose.


21-21: Added helpful comments about notification channel and logger initialization

These comments clearly explain that a notification channel is being created and that the RealtimeAnalysis function now handles its own logger initialization, which helps future developers understand the code flow.

Also applies to: 23-23

internal/logger/README.md (1)

1-938: Comprehensive logger package documentation

The README is extremely well-written and thorough, covering all aspects of the logger package including features, usage examples, configuration options, and best practices.

The documentation includes:

  • Clear explanations of the logger's features and benefits
  • Detailed examples for different usage scenarios
  • Configuration options with explanations
  • Testing utilities and approaches
  • Comprehensive best practices and application-wide logging patterns
  • Cross-platform considerations
  • Future enhancement plans

This will be very valuable for developers working with the new logging system.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~187-~187: Loose punctuation mark.
Context: ...nctions for testing: - CreateTestCore: Creates a logger core that writes to a ...

(UNLIKELY_OPENING_PUNCTUATION)

🪛 markdownlint-cli2 (0.17.2)

669-669: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

internal/api/v2/integrations.go (4)

87-87: Improved logging level categorization

The change from a generic Printf to a specific LogfWarn method improves log categorization and makes the log message more actionable.


92-92: Appropriate warning level for control channel issue

Good choice of log level for this operational issue, making it distinguishable from errors but still highlighting a potential problem.


227-227: Better error classification with structured logging

Using LogfError instead of Printf properly categorizes this as an error condition, which will improve observability and alerting capabilities.


239-239: Consistent error logging approach

This change maintains consistency with the new logging approach throughout the codebase, aiding in log aggregation and analysis.

internal/birdnet/birdnet.go (6)

48-48: Good addition of structured logger field

Adding the logger as a field in the BirdNET struct ensures consistent logging throughout the lifecycle of the object.


53-60: Well-implemented logger initialization with fallback

The constructor properly accepts a parent logger, establishing a proper hierarchy while providing a fallback to the global logger when needed.


112-113: Descriptive warning with contextual information

The warning now includes helpful context about the XNNPACK delegate failure with a recommendation, making troubleshooting easier.


362-365: Enhanced error logging with structured fields

The error message now includes additional context using key-value pairs, which improves the diagnostic value of the logs.


453-458: Simplified debug method with conditional execution

The Debug method cleanly implements conditional logging based on settings, making debug statements more performant.


460-467: Well-designed SetLogger method with proper fallback

This method allows for changing the logger after initialization, maintaining the module prefix and providing a fallback to the global logger.

internal/httpcontroller/server.go (5)

92-146: Excellent constructor pattern with explicit logger support

The new constructor ensures proper logger hierarchy and explicitly documents the preferred way to initialize the server. This promotes better logging practices throughout the codebase.


230-233: Good initialization sequence for logger dependency

The code ensures that handlers receive a logger only after it's properly initialized, preventing potential null pointer issues.


261-271: Well-implemented environment-aware development mode detection

The isDevMode method properly checks both environment variables and configuration settings, providing flexibility in how development mode is enabled.


274-281: Improved error handling with nullability check

The method properly checks if logger is initialized before using it, preventing potential null pointer exceptions.


283-351: Comprehensive logging utilities for different contexts

These helper methods create a well-structured logging hierarchy that will help with log organization and filtering. The request context logger is particularly useful for tracing requests through the system.

internal/api/v2/api.go (3)

116-146: Well-designed API initialization with logger hierarchy

The InitializeAPI function properly handles the logger dependency and creates a hierarchical logger structure, improving log organization.


203-207: Enhanced error reporting with correlation ID

The structured error logging now includes a correlation ID, which greatly improves traceability of errors across the system.


212-227: Conditional debug logging with proper formatting

The Debug method now properly handles the case where no format arguments are provided and respects the debug setting from configuration.

internal/analysis/realtime.go (27)

15-15: Use imports consistently.
The addition of the viper import is aligned with the new logging configuration. This looks correct, and it follows typical patterns for environment-based configurations.


23-23: New logger import.
Importing "github.com/tphakala/birdnet-go/internal/logger" introduces your custom logger. No immediate issues noted here.


36-44: Centralized logging configuration is a good approach.
You’ve consolidated logging settings (level, file path, JSON format, etc.) into a single config object. This will simplify changes for future logging modifications. Consider validating the config fields if they come from external sources (e.g., environment variables).


46-48: Global logger initialization.
Initializing a global logger is acceptable, but be mindful of the complexities when running concurrent tests or operations that might also need logger changes. Ensure final log flush is invoked (if needed) on application exit to avoid losing logs in buffers.


50-55: Creating hierarchical loggers.
Using appLogger := logger.Named("birdnet-go") and then child loggers like analyzerLogger := appLogger.Named("analyzer") fosters good structure. This approach helps trace logs back to their origin context.


59-59: Error logs are consistent.
You are logging the error context with "error", err. This is excellent for debugging. Ensure upstream callers handle the returned errors properly and fail fast where necessary.

Also applies to: 70-70


83-95: Contextual logs for system information.
Logging environment details (OS, hardware, threshold, overlap) at startup is very helpful for troubleshooting. This clarity is beneficial for debugging.


100-103: Hierarchical logger for datastore.
Assigning a distinct db sub-logger is a neat practice to keep logs from various subsystems well-structured.


106-112: Error handling on DB open.
The code logs the failure and returns the error promptly, ensuring the application halts if the DB connection is not ready. Using defer closeDataStore(...) is also a good pattern.


140-141: Graceful degradation logging.
Warning about buffer initialization issues or missing audio sources is valuable to help operators diagnose missing RTSP configurations.

Also applies to: 144-144


153-154: Metrics initialization error handling.
Immediately logging the error and returning ensures the system doesn’t proceed in an invalid state. This is clean and consistent with the overall style.


160-160: Initializing bird image cache with a child logger.
Passing a logger to initBirdImageCache ensures more context in logs. Make sure each submodule can also be tested individually without a global logger.


174-174: HTTP server creation with logging.
Creating the httpServer with an inherited logger is a good step for consistent logs from your web layer. No immediate issues here.


193-195: Audio capture logger usage.
Starting audio capture with a dedicated audioLogger maintains clear, structured logs for audio operations. This is well-aligned with your modular logging approach.


197-200: Clip cleanup logger usage.
Great job introducing a cleanupLogger for the cleanup routine. This helps quickly identify any clip cleanup issues in logs.


202-206: Weather polling logger usage.
Assigning a distinct weatherLogger ensures clarity if weather polling fails or needs troubleshooting.


208-211: Telemetry endpoint logger.
Consistent approach with naming loggers for discrete functionalities. This pattern fosters maintainability.


212-215: Control monitor logger usage.
A separate logger for hot reload or control signals is a nice addition, clarifying logs around dynamic configuration changes.


216-217: Quit signal monitoring.
Passing the analyzer logger to monitorCtrlC ensures a single consistent place for shutdown logs. This is beneficial for tracing final application events.


223-223: Shutdown logs.
Providing explicit logs during shutdown helps operators confirm that graceful termination took place and logs the final sequence of events.

Also applies to: 232-232


238-240: Restart logic.
Restarting audio capture is well-logged, giving operators immediate feedback that the system has begun reinitializing the audio pipeline.


245-250: Extended signature with logger parameter.
Introducing logger *logger.Logger into startAudioCapture is consistent with your new logging architecture. Consider verifying that myaudio.CaptureAudio itself can handle or at least ignore the logger if not used.


266-266: Direct call to clipCleanupMonitor.
This direct handoff is consistent. Confirm that any configuration changes in startClipCleanupMonitor do not disrupt concurrency or test scenarios.


305-318: startWeatherPolling with logger.
You log errors on weather service creation and have a plan to set the logger if the service supports injection. Looks consistent with your approach so far.


349-360: monitorCtrlC function with logger.
Encapsulating signal listening in a small function is a good separation of concerns. Logging the signal provides clarity in real-time usage and in logs.


363-369: closeDataStore with logger.
Simple but important. Logging successful closure or errors ensures consistent tracking of DB connection states.


440-450: startControlMonitor with logger.
Yet another place consistently leveraging a discrete logger. This final piece appears to align with your new logging architecture.

tphakala added 4 commits March 2, 2025 18:53
- Replace 'birdnet-go' and 'analyzer' named loggers with a single 'core' logger
- Update BirdNET initialization and realtime analysis to use consistent logger naming
- Simplify logger hierarchy and reduce redundant logger creation
- Hide server port in HTTP controller initialization

This change streamlines logging configuration and reduces complexity in logger management across the application.
- Add comprehensive sensitive data redaction for all logger methods
- Implement regex-based pattern matching for tokens, passwords, and other sensitive information
- Create dedicated functions for redacting log messages and field values
- Add unit tests to validate sensitive data handling
- Improve logging security by preventing accidental exposure of sensitive data
- Maintain existing logging structure while adding an extra layer of protection

This change introduces automatic sensitive data protection in logging, ensuring that potentially confidential information is not accidentally logged or exposed.
…ization

- Remove global.go file and associated global logger functions
- Update README.md to reflect new logger initialization approach
- Modify components to use explicit logger injection and creation
- Remove fallback mechanisms for global logger in various components
- Simplify logger initialization by requiring explicit logger creation
- Improve logger dependency management across application components

This change eliminates the global logger, enforcing more explicit and controlled logger initialization and propagation throughout the application.
…tput

- Add `ForceJSONFile` configuration option to control file log format
- Modify `CreateEncoder` to support dual-format logging
- Introduce `DualLogConfig` helper function for easy dual-format logger setup
- Update logger initialization to support human-readable console and JSON file logs
- Enhance README with comprehensive documentation on new logging features
- Refactor realtime analysis logging to use new dual-format configuration

This change provides more flexible logging capabilities, allowing developers to have human-readable console logs while maintaining structured JSON logs for analysis tools.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (20)
internal/logger/README.md (2)

5-16: Comprehensive Feature List.
The bullet‐list clearly outlines the package’s capabilities. For consistency and clarity, consider formatting key terms (e.g. using backticks for lumberjack) so that they stand out as code or library names.


183-191: Configuration Options Overview.
The configuration section lists and describes all parameters (e.g. Level, JSON, ForceJSONFile, etc.). Consider using inline code formatting (e.g. Level) for parameter names to enhance readability.

internal/logger/logger.go (8)

34-36: Consolidate imports or provide rationale for each import separately.

While functional, consider grouping standard library imports (like log, os, filepath), third-party imports (go.uber.org/zap, etc.), and local imports to improve readability.

Also applies to: 38-40


43-43: Consider adding more details to the Logger struct comment.

Currently, the comment is minimal. You might expand it to clarify how zap and sugar differ in usage so newcomers understand when to switch between structured vs. sugared logging methods.


45-48: Expose struct fields cautiously or consider making them private.

The zap, sugar, and config fields are exported, which means they can be modified outside this package. If you want to ensure immutability, consider making them private and exposing them via getters.


58-64: Segregate debug vs. info logs carefully.

Calling Info might sometimes lead to large logs in production if used for frequent events. Encourage developers to use Debug for high-volume logs.


66-72: Warn-level logs may need alerts or metrics.

Consider integrating a monitoring tool that increments a counter or triggers an alert for repeated warn logs, ensuring issues get noticed quickly.


74-80: Consider reusing code for repeated redaction.

The Error and other log methods each perform redactions. Factor out the redaction logic into a small helper to limit repetition and reduce potential for future errors.


82-88: Fatal logs should be used sparingly.

Crashing the application can be disruptive. In critical production systems, consider using graceful shutdown approaches.


195-202: Chaining "With" calls.

Chaining calls, for example logger.With(fields...).With(moreFields...), might overload logs with repeated context. Ensure usage remains consistent so you don’t inadvertently balloon logs.

internal/logger/config.go (5)

11-27: Consider default values carefully.

Providing empty or default values for log-level, color, or caller settings may mislead integrators who are unaware of the chosen defaults. Document these defaults clearly in your module’s main README.


42-52: Consider differentiating ProductionConfig from DefaultConfig more.

Right now, the only changes are the log level, JSON format, and color. If you foresee a reason to diverge further, define those in synergy with your environment detection logic.


95-98: Time format: No timezone or milliseconds.

Some operators need finer granularity. Consider providing a config option to switch to a more detailed time format if required.


116-126: Default log level in dev.

Ensuring DebugLevel is used in dev mode is consistent with your doc. Double-check that no unrelated system logs get overloaded in dev.


128-146: Using color in console logs is beneficial.

Make sure there are fallback indicators for visually impaired users if color is disabled. Possibly consider extended text or a tag to identify severity in the console.

internal/analysis/realtime.go (4)

140-145: Warning for partial buffer initialization
Informing the user that some audio sources might be unavailable is helpful. Consider adding more context if certain sources fail frequently.


165-172: Commented-out logger injection
Consider fully implementing a SetLogger method on processor or removing these lines if injection is not needed.


180-186: Buffer manager logging
Similar to the processor, you might optionally introduce a SetLogger method to the buffer manager instead of leaving these references commented.


271-303: Implementation of clipCleanupMonitor
Uses a 5-minute ticker and handles both age-based and usage-based cleanup. This is clear and maintainable. If you anticipate making this interval configurable, consider sourcing that from settings.

internal/imageprovider/wikipedia.go (1)

103-107: queryAndGetFirstPage method
Well-structured checks for empty or invalid responses. Logging intermediate steps is valuable for debugging. Ensure you’re not inadvertently printing overly large data sets in logs.

Also applies to: 112-116, 119-123, 127-139, 141-166

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 212c8f3 and 1702d78.

📒 Files selected for processing (20)
  • internal/analysis/birdnet_init.go (1 hunks)
  • internal/analysis/realtime.go (8 hunks)
  • internal/api/v2/api.go (7 hunks)
  • internal/diskmanager/README.md (1 hunks)
  • internal/diskmanager/file_utils.go (4 hunks)
  • internal/diskmanager/policy_age.go (6 hunks)
  • internal/diskmanager/policy_usage.go (8 hunks)
  • internal/httpcontroller/middleware.go (7 hunks)
  • internal/httpcontroller/server.go (6 hunks)
  • internal/httpcontroller/server_logger.go (1 hunks)
  • internal/httpcontroller/template_renderers.go (9 hunks)
  • internal/imageprovider/imageprovider.go (17 hunks)
  • internal/imageprovider/wikipedia.go (7 hunks)
  • internal/logger/README.md (1 hunks)
  • internal/logger/config.go (1 hunks)
  • internal/logger/logger.go (1 hunks)
  • internal/logger/rotation.go (1 hunks)
  • internal/logger/sensitive.go (1 hunks)
  • internal/logger/sensitive_test.go (1 hunks)
  • internal/logger/testing.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/analysis/birdnet_init.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/logger/testing.go
  • internal/httpcontroller/template_renderers.go
  • internal/logger/rotation.go
  • internal/imageprovider/imageprovider.go
  • internal/api/v2/api.go
🧰 Additional context used
🪛 LanguageTool
internal/logger/README.md

[uncategorized] ~200-~200: Loose punctuation mark.
Context: ...nctions for testing: - CreateTestCore: Creates a logger core that writes to a ...

(UNLIKELY_OPENING_PUNCTUATION)

internal/diskmanager/README.md

[duplication] ~26-~26: Possible typo: you repeated a word.
Context: ...icy_age_test.go** - Tests for age-based policy - policy_usage_test.go - Tests for usage-based...

(ENGLISH_WORD_REPEAT_RULE)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
🔇 Additional comments (110)
internal/logger/README.md (32)

1-3: Clear Introduction Header.
The header and introductory statement immediately communicate the purpose of the package and that it wraps Uber’s Zap for structured logging.


17-25: Well-Organized Package Structure.
The “Package Organization” section provides a concise breakdown of the files (such as config.go, logger.go, rotation.go, and testing.go). This helps users quickly understand the repository layout.


26-33: Basic Usage – Import Statement.
The code snippet showing how to import the package is straightforward and easy to follow.


34-52: Example: Logger Initialization and Usage.
This section clearly demonstrates creating a logger using the default configuration, error handling, and invoking various log methods (including the use of log.Fatal, with a helpful comment noting that it calls os.Exit(1)).


54-88: Custom Logger Configuration Example.
The custom logger example illustrates both console-only and file-with-rotation setups. It might be useful to add a brief note describing the difference in behavior between development and production configurations.


90-116: Sharing Loggers Across Components.
The example of creating a root logger and then generating named child loggers for different components (using Named) is clear and emphasizes modularity in logging.


118-129: Structured Logging Usage.
The snippet demonstrating the use of structured key-value pairs (e.g. "user_id", 123) is succinct and illustrates the intended usage perfectly.


131-144: Named Loggers with Nested Notation.
The documentation shows two approaches for naming loggers (chaining with .Named() and dot notation). This dual example is very useful for developers to understand the flexibility in naming.


146-157: Persistent Context in Loggers.
The “With Context” example clearly demonstrates how additional fields (like user_id and session_id) can be injected into all subsequent log entries. This is an effective pattern for maintaining context.


159-181: Log Rotation Implementation.
The log rotation snippet—showing the creation of a rotation configuration and its integration into the logger—is well-documented. Error handling is appropriately included.


192-232: Testing Guidelines and Helpers.
The testing section is comprehensive; it details how to create test cores, configure a logger for tests, and capture log output. This will greatly assist in ensuring the logging functionality is correctly integrated and behaves as expected.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~200-~200: Loose punctuation mark.
Context: ...nctions for testing: - CreateTestCore: Creates a logger core that writes to a ...

(UNLIKELY_OPENING_PUNCTUATION)


233-244: Mocked Filesystems for Testing.
The example of using afero to create an in-memory filesystem for testing file operations is a nice touch. It shows attention to cross-platform and environment-independent testing.


245-252: Running the Logger Tests.
The instructions for executing the tests (using go test) are clear and provide a direct command for developers to verify functionality.


253-267: Logging Best Practices.
The “Good Practices” section offers solid recommendations such as deferring log.Sync() and using structured logging consistently. These guidelines will help maintain high-quality log output during both development and production.


269-280: Component-Specific Logger Examples.
The guidelines for creating separate loggers (like dbLogger and authLogger) exemplify how to organize logs by application component. This supports easier debugging and log filtering.


282-290: Helper Functions for Configuration.
The examples using logger.DefaultConfig() and logger.ProductionConfig() succinctly show how to quickly configure the logger for different environments.


291-299: Sensitive Data Handling Overview.
The section clearly explains how sensitive information is automatically redacted, which is crucial for maintaining security in log files.


300-314: Sensitive Data Redaction Example.
The before-and-after code example effectively demonstrates how sensitive fields (like tokens) are transformed into a redacted format, making the behavior very clear.


315-327: List of Recognized Sensitive Data.
This bulleted list is comprehensive and well-organized. It gives developers confidence that common sensitive items (e.g. passwords, API keys) will be appropriately handled.


328-338: Custom Sensitive Data Handling.
The example highlighting the use of logger.RedactSensitiveData and logger.RedactSensitiveFields is straightforward. A brief mention of the expected input types or use cases might further enhance clarity.


339-347: Security Considerations.
This section reinforces the importance of cautious logging practices and provides actionable recommendations to prevent inadvertent exposure of sensitive data.


349-376: Application-Wide Logging Best Practices.
The detailed guidelines for managing logger initialization, dependency injection, and hierarchical logger naming across the application are thorough and very useful.


377-384: Logger Initialization in Components.
The code example demonstrates proper error handling while initializing a logger when one is not provided. This defensive approach improves robustness.


388-404: Real-World Example – Dual-Format Logging.
The complete example from the real-time analysis module shows how to set up dual-format logging (human-readable console output and JSON file logs). This practical demonstration makes the advanced features very accessible.


611-618: Cross-Platform File Paths.
The snippet advises against using platform-specific path separators and recommends filepath.Join for portability. This is a valuable tip for developers targeting multiple operating systems.


620-631: File Permissions and Cross-Platform Compatibility.
Using a constant (like const filePerms = 0644) to set file permissions improves both readability and maintainability across different platforms.


633-642: Terminal Colors Support.
The example illustrates how to conditionally disable colored output on Windows when terminal support is lacking. This ensures consistent behavior across environments.


649-651: File Locking Considerations.
A brief note on the need for platform-specific file locking is helpful. Although the snippet is short, it nudges developers to consider concurrency issues when handling log files.


653-660: Normalization of Line Endings.
The guidelines for handling different line endings (e.g. Windows vs. Unix) are a thoughtful addition, ensuring log consistency and easier parsing across platforms.


667-669: Environment Variable Conventions.
The suggestion to use uppercase with underscores for environment variables (like LOG_LEVEL) follows common best practices and improves clarity in configuration management.


676-704: Process Signals Handling.
The signal handling snippet demonstrates cross-platform considerations, distinguishing between Windows and Unix-like systems. It correctly outlines the difference in behavior for SIGHUP versus other signals. For complex applications, it might be beneficial to document further customization options.


708-761: Implementation Notes and Future Enhancements.
The final sections provide valuable context regarding the internal decisions (such as using Zap and lumberjack) and outline areas for future improvements (like advanced rotation strategies and multiple output destinations). This roadmap is beneficial for ongoing maintenance and evolution of the logging system.

internal/logger/sensitive_test.go (2)

7-63: Well-structured comprehensive test for RedactSensitiveData

This test function comprehensively covers various sensitive data patterns including empty strings, regular messages, and different types of sensitive data (bearer tokens, JWT tokens, API keys, passwords, session cookies, and CSRF tokens).

The table-driven test approach makes it easy to add additional test cases in the future. Each test case has a clear name and expected output.


65-125: Thorough test coverage for RedactSensitiveFields

The test for RedactSensitiveFields covers a good range of scenarios including empty slices, non-sensitive fields, various sensitive field types, mixed case handling, and non-string values.

The explicit length check before element comparison provides better error reporting, making test failures easier to diagnose.

internal/httpcontroller/middleware.go (4)

31-60: Good implementation of structured logging with proper context

The new logger methods provide a clean abstraction for component-specific and request-specific logging. The request logger includes useful context like request ID, client IP, method, and path.

The UUID generation for request IDs ensures unique tracking across requests, and setting the header allows for consistent tracing throughout the request lifecycle.


88-109: Enhanced CSRF error logging with redaction safeguards

The CSRF error logging now captures detailed context while ensuring sensitive data is properly redacted. The fallback for development mode includes additional safety by only logging non-sensitive parts of the request.

This approach provides valuable debugging information while maintaining security best practices.


140-154: Improved cache control middleware logging

The middleware now uses structured logging with request-specific context, making it easier to trace and debug cache-related issues. The conditional logging based on development mode helps reduce noise in production while providing detailed information when needed.


222-255: Comprehensive authentication logging

The auth middleware now provides detailed, contextual logging for authentication decisions. The inclusion of redirect paths and HTMX status in logs will make troubleshooting authentication issues much easier, especially in complex browser interactions.

internal/diskmanager/file_utils.go (1)

38-55: Good DiskManager struct design with proper logger handling

The new DiskManager struct and constructor are well designed. The constructor takes an optional parent logger and creates a properly named component logger, allowing for hierarchical logging.

This pattern enables better organization of logs and proper context for all disk management operations.

internal/logger/sensitive.go (4)

9-23: Comprehensive regex patterns for sensitive data detection

The SensitiveDataPatterns array contains well-crafted regular expressions for identifying common types of sensitive data including authentication tokens, API keys, and session information.

The patterns are specific enough to match common formats while being flexible enough to catch variations, and the use of case-insensitive matching ((?i)) is appropriate.


25-29: Complete list of sensitive keywords for field detection

The SensitiveKeywords list covers all common names for sensitive fields. This comprehensive list ensures that fields containing credentials, tokens, and other sensitive information will be properly identified for redaction.


31-43: Efficient and safe sensitive data redaction function

The RedactSensitiveData function handles empty inputs correctly and applies all regex patterns efficiently. The approach of preserving the prefix (like "Bearer " or "password=") while redacting only the sensitive part is user-friendly and maintains log readability.


45-71: Well-designed field redaction with defensive programming

The RedactSensitiveFields function shows good attention to detail:

  1. Creates a copy to avoid modifying the original data
  2. Handles key-value pairs properly
  3. Performs case-insensitive matching
  4. Only redacts string values
  5. Preserves non-string values unchanged

The implementation is robust and handles edge cases appropriately.

internal/diskmanager/policy_usage.go (3)

19-34: Good implementation of deprecation notice and early return

The deprecation notice clearly guides users to the newer DiskManager.UsageBasedCleanup method. The early check for usage policy is an effective way to avoid unnecessary processing.


43-46: Well-structured logging with contextual fields

The conversion to structured logging with named fields (like base_dir, threshold, usage) provides much richer context than simple string messages, making logs more searchable and analyzable.

Also applies to: 56-59, 80-83


228-294: Good encapsulation in the DiskManager struct

Moving the cleanup functionality into a method of the DiskManager struct is a good design choice that:

  1. Maintains consistent access to the logger through dm.Logger
  2. Provides cleaner access to the database through dm.DB
  3. Improves code organization by grouping related functionality

The implementation maintains the same core logic while transitioning to the new logging system.

internal/diskmanager/policy_age.go (4)

122-167: Improved function organization with clear responsibilities

The new DiskManager.AgeBasedCleanup method is well-structured with:

  1. Clear separation of configuration and execution logic
  2. Improved error handling with contextual logging
  3. Smart creation of a lookup map for locked clips
  4. Delegation to specialized methods for the actual processing

This is a good example of the single responsibility principle in action.


169-217: Well-factored batch processing logic

Breaking out the file processing logic into a separate method with its own responsibility improves maintainability and readability. The method handles interruption signals cleanly and maintains proper state tracking.


219-277: Good separation of single-file processing logic

The processFileForDeletion method:

  1. Has clear, focused responsibility for a single file
  2. Uses early returns effectively to handle skip conditions
  3. Provides detailed debug logging with context
  4. Returns a boolean indicating success and an error when needed

This is a good example of modular, maintainable code.


279-290: Effective extraction of utility function

Moving the species count map creation to a separate function improves code reuse and readability. This is a good application of the DRY principle.

internal/httpcontroller/server_logger.go (4)

14-26: Good adapter implementation for Echo's logging system

The echoLogAdapter effectively bridges the gap between Echo's expected io.Writer interface and the custom logger. The implementation properly handles whitespace trimming and empty message filtering.


28-47: Well-designed debug logging with fallback mechanism

The Debug method:

  1. Only logs when in development mode for efficiency
  2. Handles both formatted and unformatted messages correctly
  3. Falls back to standard logging when the custom logger is unavailable
  4. Properly handles variable arguments

This ensures logging works consistently throughout the application lifecycle.


49-70: Clean logger initialization with proper configuration

The initLogger method checks if logging is enabled, detects development mode, and correctly configures the Echo logger with the custom adapter. The early return when logging is disabled is a good optimization.


72-165: Comprehensive HTTP request logging configuration

The request logging implementation is excellent with:

  1. Intelligent log level selection based on status codes
  2. Inclusion of essential request metadata (method, URI, latency)
  3. Conditional inclusion of detailed fields based on response status
  4. Proper extraction and inclusion of request IDs for correlation
  5. Smart handling of path parameters

This provides valuable observability without excessive logging.

internal/httpcontroller/server.go (5)

92-143: Excellent logger-aware constructor implementation

The NewWithLogger constructor:

  1. Creates a proper hierarchical logger structure
  2. Sets default settings early
  3. Initializes handlers with a properly named child logger
  4. Logs server initialization with relevant context

This ensures consistent logging throughout the server's lifecycle.


227-232: Good logger initialization sequence

Setting the logger in handlers after server initialization ensures all components have access to the properly configured logger. This avoids potential null reference issues and maintains logging consistency.


259-269: Smart development mode detection with environment override

The isDevMode method properly checks environment variables first before falling back to configuration settings. This provides flexibility for runtime environment changes without configuration updates.


271-279: Improved error handling with structured logging

The updated handleServerError method now uses the structured logger when available, ensuring consistent error reporting format throughout the application.


281-349: Well-organized logger hierarchy creation

The helper methods for creating component-specific loggers:

  1. Maintain a consistent hierarchical naming structure (e.g., "http.api.v1")
  2. Properly handle the case when no logger is available
  3. Add relevant context fields to request loggers
  4. Intelligently determine logger components based on request paths

This ensures logs are properly categorized and contain relevant context.

internal/logger/logger.go (6)

2-30: Great documentation block to introduce the logger package.

This comprehensive overview helps developers quickly understand the package usage and features.


50-56: Redaction approach looks good.

Using RedactSensitiveData and RedactSensitiveFields ensures sensitive information is protected. Encourage thorough tests to verify that typical personally identifiable information and other secrets are appropriately redacted.


90-184: Ensure concurrent safety for file writes in multi-goroutine contexts.

While zap cores are generally safe for concurrent use, verify your usage patterns—particularly with file rotation across threads—to prevent potential race conditions.


186-193: Named loggers are helpful for grouping logs by component.

This approach is beneficial for debugging microservices and complex systems. No issues spotted here.


204-207: Remember to call Sync() upon shutdown.

l.zap.Sync() ensures all buffers are flushed. Consider placing this in a deferred call in your main() function or top-level server code.


209-213: Returning the raw zap logger.

Exposing the underlying zap logger is flexible but can lead to bypassing your redaction logic. Remind developers to remain consistent with your sanitization approach.

internal/diskmanager/README.md (1)

1-323: Well-structured documentation.

This README thoroughly explains the disk manager's usage, configuration, and integration with other parts of the system. No major issues. One minor note: double-check for any repeated words or small typos flagged by tooling.

🧰 Tools
🪛 LanguageTool

[duplication] ~26-~26: Possible typo: you repeated a word.
Context: ...icy_age_test.go** - Tests for age-based policy - policy_usage_test.go - Tests for usage-based...

(ENGLISH_WORD_REPEAT_RULE)

internal/logger/config.go (8)

1-2: File layout is clear.

This file is introduced to hold all logger configuration logic. Keeping these configs distinct is a good design choice.


29-40: “DefaultConfig” might specify a log level.

Leaving Level as an empty string can lead to a fallback. If that fallback is DebugLevel for dev, clarify in the docs to avoid confusion.


55-74: RotationConfig is straightforward.

No concerns. The structure covers all major rotation parameters. Ensure that validation for max values or negative values is handled upstream, if needed.


77-93: “createEncoderConfig” is well-structured.

This function sets sensible defaults. The short caller encoding is helpful for local debugging. No concerns noted.


100-114: Zap options handle Dev mode well.

Use zap.Development() with caution, as it changes DPanic behavior. Document this to ensure devs understand the difference.


148-160: CreateEncoder usage.

This function logically splits out JSON vs console logic. Looks good. Just note that if someone passes an empty file path but sets ForceJSONFile to true by accident, the code might create console logs in JSON format.


162-173: Graceful fallback for invalid log levels.

The fallback to defaultLevel is a nice approach. No issues.


175-197: DualLogConfig helps unify logging.

Combining human-readable console output with structured JSON for files is a powerful approach. Nicely done.

internal/analysis/realtime.go (30)

15-23: Use of viper and logger imports
These new imports are consistent with the PR objective of integrating a structured logging system and reading config values from Viper.


32-35: Initialization of err variable
Defining and reusing the err variable within this scope is standard practice. No issues found here.


36-46: Logger configuration block
Using viper.GetString("log.level") and other retrieved values to populate the logger.Config struct seems correct. Make sure these keys (log.level, log.disable_color, etc.) are documented and validated for correctness.


47-51: Initializing a new logger instance
The error handling and fallback mechanism are appropriate. Good job returning early on error.


53-55: Named logger usage
Using logger.Named("core") for the realtime analyzer is a good approach to differentiate logs from various components.


59-59: Error logging with context
Including the "error", err field is consistent with structured logging best practices.


70-70: Consistent use of structured logging for errors
Good usage of coreLogger.Error to log errors with context.


84-88: Logging system details
Populating fields like os, platform, version, and hardware is helpful for troubleshooting.


90-95: Logging analyzer configuration
Logging BirdNET threshold, overlap, and other configs is useful for diagnosing runtime configuration issues.


100-103: Hierarchical naming for datastore logger
This approach helps group datastore-related log messages. Make sure calls to dataStore methods produce meaningful logs.


111-111: Ensuring database closure
Deferring closeDataStore is a good pattern to avoid leaving connections open.


153-154: Metrics initialization error logging
Consistent with the rest of your error logging approach.


160-160: Passing logger to initBirdImageCache
Providing a scoped logger for image caching helps keep logs organized.


173-176: HTTP server logging
Initializes the HTTP server with a structured logger. You might want to confirm if the server logs relevant startup errors or warnings.


193-195: Audio capture logger
Creating a separate audioLogger is a good way to isolate audio device logs. Verify whether CaptureAudio itself will eventually accept this logger.


197-200: Clip cleanup monitor start
Providing a specialized logger for the cleanup routine helps with clarity.


202-205: Weather polling logger
A separate logger for weather polling is consistent with the modular logging strategy.


209-210: Telemetry endpoint logger
Great use of a named logger for telemetry. Properly logs the endpoint’s lifecycle events.


214-214: Control monitor logging
startControlMonitor with a dedicated logger is consistent.


217-217: SIGINT monitor with logger
Nice improvement over silent termination logs. Helps with debugging shutdown events.


223-223: Shutdown log statements
Logging shutdown progress at key points is excellent for diagnosing graceful termination issues.

Also applies to: 232-232


236-240: Restarting audio capture
Good use of separate log statements to indicate restarts and re-initialize capture.


245-254: Signature update for startAudioCapture
Including a logger parameter fosters consistency and helps unify logs for audio capture.


257-269: Enhanced startClipCleanupMonitor
Spawning a diskmanager with a named logger ensures disk operations are logged distinctly.


306-325: Weather polling routine signature
Passing the logger in ensures all weather service logs have consistent context fields.


327-348: Telemetry endpoint routine
Clean approach for toggling telemetry. Use of structured logging for both enabled and disabled states is clear.


350-354: Monitor Ctrl+C signature and log message
Properly logs Ctrl+C interrupts and signals downstream goroutines to exit.

Also applies to: 358-358


364-369: Safe database closure with structured logs
This change helps identify any error during DB closure—good practice.


373-399: initBirdImageCache signature and structured logging
Integrating the logger ensures full visibility into image cache initialization, including species fetch progress.


441-449: Control monitor injection
Passing a logger to the control monitor can provide better traceability if dynamic changes occur at runtime.

internal/imageprovider/wikipedia.go (6)

16-17: Introducing logger field
Replacing the standard log with a *logger.Logger field aligns with the new unified logging system and ensures consistent, structured log output.

Also applies to: 27-28


38-61: NewWikiMediaProvider with parent logger
The hierarchical logging approach is effective, naming the logger imageprovider.wikipedia. Rate limiting and client initialization also look correct.


68-72: queryWithRetry logging
Emphasizing debug logs under a debug boolean is a good pattern—this avoids spamming logs in production.

Also applies to: 86-91


168-234: Fetch method
Comprehensive logging at each step—thumbnail retrieval, error handling, author info. Fallback to an image without attribution if author data fails is a user-friendly design.


236-304: queryThumbnail method
Clearly logs each step in retrieving a thumbnail. The fallback logic for missing or invalid URLs is well-defined.


306-412: queryAuthorInfo method
Structured logging of both success and failures. Returning partial results if the license or author fields are missing can improve user experience.

Comment on lines +174 to 175
diskLogger.Info("Sorted files written", "file_path", filePath)
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential nil pointer dereference in info log

The diskLogger.Info() call doesn't check if diskLogger is nil before using it, which could cause a panic if the logger isn't initialized.

-	diskLogger.Info("Sorted files written", "file_path", filePath)
+	if diskLogger != nil {
+		diskLogger.Info("Sorted files written", "file_path", filePath)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
diskLogger.Info("Sorted files written", "file_path", filePath)
return nil
if diskLogger != nil {
diskLogger.Info("Sorted files written", "file_path", filePath)
}
return nil

Comment on lines +70 to 71
diskLogger.Debug("Found protected clips", "count", len(lockedClips))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential nil pointer dereference in debug log

The diskLogger.Debug() call doesn't check if diskLogger is nil before using it, which could cause a panic if the logger isn't initialized.

	if debug {
-		diskLogger.Debug("Found protected clips", "count", len(lockedClips))
+		if diskLogger != nil {
+			diskLogger.Debug("Found protected clips", "count", len(lockedClips))
+		}
	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
diskLogger.Debug("Found protected clips", "count", len(lockedClips))
}
if debug {
if diskLogger != nil {
diskLogger.Debug("Found protected clips", "count", len(lockedClips))
}
}

Comment on lines +59 to 61
// Initialize logger if it hasn't been initialized

var files []FileInfo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Incomplete logger initialization comment

The comment "Initialize logger if it hasn't been initialized" suggests code should follow to initialize the logger, but no such initialization exists. This misleading comment should either be removed or the missing initialization code should be added.

-	// Initialize logger if it hasn't been initialized
+	// Ensure logger is available
+	if diskLogger == nil {
+		diskLogger = logger.NewLogger()
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Initialize logger if it hasn't been initialized
var files []FileInfo
// Ensure logger is available
if diskLogger == nil {
diskLogger = logger.NewLogger()
}
var files []FileInfo

Comment on lines +35 to 37
// diskLogger is a named logger for disk operations
var diskLogger *logger.Logger

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing initialization for diskLogger global variable

The global diskLogger variable is declared but never initialized before being used. This could lead to nil pointer dereferences when methods are called on it.

Consider initializing the logger in an init() function or modifying functions like GetAudioFiles and WriteSortedFilesToFile to use the logger from the new DiskManager struct instead of the global variable.

+func init() {
+	diskLogger = logger.NewLogger()
+}

Or, better yet, refactor the functions to be methods on the DiskManager struct to use its logger:

-func GetAudioFiles(baseDir string, allowedExts []string, db Interface, debug bool) ([]FileInfo, error) {
+func (dm *DiskManager) GetAudioFiles(baseDir string, allowedExts []string, debug bool) ([]FileInfo, error) {
     // Use dm.Logger instead of diskLogger
     // Use dm.DB instead of the db parameter
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// diskLogger is a named logger for disk operations
var diskLogger *logger.Logger
// diskLogger is a named logger for disk operations
var diskLogger *logger.Logger
func init() {
diskLogger = logger.NewLogger()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant