Skip to content

feat: add log output destinations (--log-outputs) and file rotation - #166

Merged
JoshVanL merged 11 commits into
dapr:mainfrom
nelson-parente:feat/log-file-tee-and-rotation
Sep 3, 2026
Merged

feat: add log output destinations (--log-outputs) and file rotation#166
JoshVanL merged 11 commits into
dapr:mainfrom
nelson-parente:feat/log-file-tee-and-rotation

Conversation

@nelson-parente

@nelson-parente nelson-parente commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Adds two independent, opt-in capabilities to log output. All new flags default to off/empty, so behaviour with no new flags is unchanged.

1. --log-outputs — a list of log destinations

Per review, destinations are a list rather than a tee bool: --log-outputs takes a comma-separated list of stdout, stderr, or file paths. --log-file merges into that list, so the two flags compose and existing behaviour is unchanged; the tee use case is --log-outputs=stdout,/path/to/file.

Destinations are deduplicated (two writers on one path would double every line and corrupt rotation), and console destinations order before files — io.MultiWriter stops at the first failing writer, so this keeps console output alive when file writes start failing (a full disk, for example). Rotation and compression apply to every file destination.

2. --log-file-max-* / --log-file-compression — rotation

--log-file opens the file O_APPEND and never rolls it, so long-lived components grow it without bound. Rotation is delegated to lumberjack:

flag meaning
--log-file-max-size megabytes before rotation
--log-file-max-backups rotated files to keep
--log-file-max-age days to retain rotated files
--log-file-compression none (default) or gzip for rotated files

When none is set, newFileWriter returns a plain append-mode *os.File — the existing code path, unchanged.

File permissions are kept identical with rotation on or off

lumberjack creates missing files as 0600 (and preserves the mode of existing ones), where the plain path creates 0644. Left alone, enabling rotation would silently change new log-file permissions and break log shippers tailing the file as a non-owner user. newFileWriter therefore pre-creates the file with the same flags and mode as the plain path; rotated archives and post-rotation files inherit that mode. A test asserts mode parity between the two paths (comparing them to each other, so it is immune to umask and Windows permission semantics).

Misconfiguration warns instead of silently no-opping

Rotation or compression flags with no file destination configured (neither --log-file nor a file entry in --log-outputs) now log a warning through a dapr.kit.logger logger. Warn rather than error: an error here would turn a harmless misconfiguration into a startup failure for every binary that attaches these flags. The logger is fetched at the top of ApplyOptionsToLoggers, before the registry snapshot, so the warning follows the configured format, level and output of that apply.

Typed fields behind separate flag receivers

Per review: the optional fields are properly typed and unexported — *uint rotation limits (nil = flag not provided) and a none|gzip compression enum — with the CLI flags attached to separate unexported string receivers that validate() parses at apply time, mirroring the pattern in dapr/dapr cmd/daprd/options. AttachCmdFlags keeps its (stringVar, boolVar) signature, so no caller changes anywhere. Invalid values fail validation with a clear error naming the flag, before any logger is mutated.

On lumberjack

gopkg.in/natefinch/lumberjack.v2 is MIT-licensed and the de-facto Go rotation library (it is what the zap ecosystem uses). It is low-activity because it is feature-complete rather than unmaintained. Behaviours worth knowing, documented alongside the flags:

  • when only --log-file-max-age or --log-file-compression is set, lumberjack applies its own 100MB default MaxSize
  • cleanup runs at rotation time, not at process start
  • rotation renames files, so the log path must be on a per-pod volume — pointing multiple replicas at one shared RWX path will lose data

Notes

  • setLogOutput now takes *Options rather than a bare path, since it needs more than the path. logOutputFile *os.File becomes logOutputCloser io.Closer so it can close either writer type. The redirect-before-close ordering is preserved.
  • consoleWriter is introduced as a package-level io.Writer (defaulting to os.Stdout) purely so tests can capture console output.
  • The exported Logger interface is untouched.
  • History note: the branch evolved through review (tee bool → destinations list; string fields → typed fields behind separate receivers); happy to squash on merge.

Test plan

  • go test -race ./logger/... passes, including -count=2 for order-independence; logger package coverage ~90%
  • Behavioural rotation test: drives ~3MB at max-size=1MB and asserts on disk that an archive appeared and the active file rolled (deliberately size-based only — compression and MaxBackups pruning run on a background goroutine and would be timing-flaky in CI)
  • Full flag-set registration asserted (add/remove/rename fails the test deliberately), destination union/dedupe/stderr, outputs+rotation combined, permission parity, misconfiguration warning, invalid values rejected
  • golangci-lint run ./logger/... adds zero new findings versus main (the 9 remaining goconst hits are pre-existing)
  • Existing file-output tests unchanged and passing, confirming defaults are untouched
  • Note: concurrency and events/ratelimiting fail to build on main today, unrelated to this change

@nelson-parente
nelson-parente marked this pull request as ready for review September 1, 2026 09:20
@nelson-parente
nelson-parente requested review from a team as code owners September 1, 2026 09:21
javier-aliaga
javier-aliaga previously approved these changes Sep 1, 2026
Comment thread logger/options.go Outdated
Comment thread logger/options.go Outdated
Comment thread logger/options.go Outdated
Comment thread logger/options.go Outdated
Comment thread logger/options.go Outdated
Comment thread logger/options.go Outdated
@JoshVanL

JoshVanL commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

@nelson-parente please resolve conflicts

When --log-file is set the logger currently replaces the console writer,
so logs stop appearing in kubectl logs. --log-file-tee keeps both
destinations via io.MultiWriter.

The console writer is listed first because io.MultiWriter stops at the
first failed writer: this way console output survives file write failures
such as a full disk.

Default is false, so existing behaviour is unchanged.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
--log-file opens the file in append mode and never rolls it, so long-lived
components grow it without bound. This adds rotation via lumberjack behind
four new flags:

  --log-file-max-size      megabytes before rotation
  --log-file-max-backups   rotated files to keep
  --log-file-max-age       days to retain rotated files
  --log-file-compress      gzip rotated files

When none of them is set the writer stays a plain append-mode *os.File, so
existing behaviour is byte-for-byte unchanged.

The three numeric options are string-typed and parsed at apply time so that
AttachCmdFlags keeps its (stringVar, boolVar) signature. That keeps the kit
bump non-breaking and surfaces the flags on every binary with no caller
changes.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
t.Cleanup runs LIFO. The tee tests registered their cleanup before calling
t.TempDir, so TempDir's RemoveAll ran first — while the log file was still
open — and Windows cannot delete an open file:

  TempDir RemoveAll cleanup: unlinkat ...\dapr.log: The process cannot
  access the file because it is being used by another process.

Calling t.TempDir first registers its cleanup first, so it runs last, after
the cleanup that closes the file. The rotation test already had this
ordering, which is why only the two tee tests failed.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
The existing rotation tests asserted only that the lumberjack struct was
populated correctly. That would still pass if the rotating writer were never
installed as the log output, or if MaxSize were interpreted in the wrong
unit, so it verified configuration rather than behaviour.

Adds three gaps:

- TestFileRotationActuallyRotates drives ~3MB through the configured logger
  with max-size=1MB and asserts on disk that an archive appeared and the
  active file was rolled. Only size-based rotation is asserted: lumberjack
  rotates synchronously on the write that exceeds MaxSize, whereas
  compression and MaxBackups pruning run on a background goroutine and would
  make the assertion timing-dependent.

- TestOptions/registers_the_exact_set_of_log_flags asserts the full
  registered flag set rather than spot-checking names. Flag names become D3E
  chart annotations, so a rename is a breaking change for anyone who has
  already set them; this makes that fail deliberately rather than silently.

- TestTeeWithRotation covers file output that both rotates and tees, which is
  the combination actually configured in the field. Nothing previously
  exercised the MultiWriter-wrapping-lumberjack composition.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
…rotation

Two behaviours surfaced by review:

1. Setting --log-file-tee or any rotation flag without --log-file was a
   silent no-op. Now it logs a warning through a dapr.kit.logger logger.
   Warn rather than fail: an error here would turn a harmless
   misconfiguration into a startup failure for every binary that attaches
   these flags. The logger is fetched at the top of ApplyOptionsToLoggers,
   before the registry snapshot, so it always follows the configured
   format, level and output for that apply.

2. lumberjack creates missing log files as 0600 (and preserves the mode of
   existing ones), where the non-rotating path creates 0644. Enabling
   rotation would therefore silently change new log file permissions and
   break log shippers tailing the file as a non-owner user. newFileWriter
   now pre-creates the file with the same flags and mode as the plain
   path, so permissions are identical whether or not rotation is enabled;
   rotated archives and post-rotation files inherit that mode.

The permission test compares the plain and rotating paths against each
other rather than asserting an absolute mode, so it is immune to umask
and Windows permission semantics.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
Review feedback: the rotation values are semantically unsigned, so parse
them with strconv.ParseUint (bit size 31 keeps the int conversion safe on
32-bit platforms) instead of Atoi plus a sign check. The struct fields stay
string-typed because they bind through AttachCmdFlags(stringVar, boolVar);
widening that signature would break every existing caller.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@nelson-parente
nelson-parente force-pushed the feat/log-file-tee-and-rotation branch from 1193833 to aa64af5 Compare September 2, 2026 13:40
Rebase onto main after dapr#167 merged: the exact-flag-set test failed as
designed on the new flag; register it in the expected set.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@nelson-parente
nelson-parente force-pushed the feat/log-file-tee-and-rotation branch from aa64af5 to 23ea94d Compare September 2, 2026 14:00
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.63636% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.80%. Comparing base (1bef456) to head (23ea94d).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
logger/options.go 93.63% 5 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #166      +/-   ##
==========================================
+ Coverage   72.37%   72.80%   +0.42%     
==========================================
  Files          92       92              
  Lines        6537     6666     +129     
==========================================
+ Hits         4731     4853     +122     
- Misses       1617     1622       +5     
- Partials      189      191       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…ession enum

Review feedback: the optional fields are now properly typed and unexported —
*uint rotation limits (nil = not provided) and a none|gzip compression
enum — with the CLI flags attached to separate unexported string receivers
that validate() parses at apply time, mirroring the pattern in dapr/dapr
cmd/daprd/options. AttachCmdFlags keeps its (stringVar, boolVar) signature,
so no caller changes anywhere.

--log-file-compress (bool) becomes --log-file-compression=none|gzip, which
leaves room for other codecs without a flag break. Invalid values fail
validation before any logger is mutated.

Behaviour is unchanged: an explicit 0 still disables the corresponding
limit, and with no rotation engaged the writer stays the plain append-mode
file.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@nelson-parente
nelson-parente force-pushed the feat/log-file-tee-and-rotation branch from b532821 to 662b755 Compare September 2, 2026 14:17
Review feedback: rather than a tee bool, log destinations are a list.
--log-outputs takes a comma-separated list of "stdout", "stderr", or file
paths; --log-file merges into that list, so both flags compose and existing
behaviour is unchanged. The previous tee semantics are expressed as
--log-outputs=stdout,/path/to/file.

Destinations are deduplicated (two writers on one path would double every
line and corrupt rotation) and console destinations order before files, so
io.MultiWriter keeps console output alive when file writes start failing.
Rotation and compression options apply to every file destination.

The inert-option warning now keys on "no file destination configured"
rather than --log-file specifically.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@nelson-parente nelson-parente changed the title feat: add --log-file-tee and file rotation options feat: add log output destinations (--log-outputs) and file rotation Sep 3, 2026
…e open-failure unwind

Pre-merge review findings:

- The three rotation usage strings still said "No effect without
  --log-file", but the actual gating (and the warning) treat a file entry
  in --log-outputs as an equally valid destination. All four file-option
  usage strings now say "a file destination".

- The mid-list open-failure unwind in setLogOutput (close already-opened
  files, leave every logger on its previous output) had no test. Added one
  that applies destinations [stdout, <directory>] and asserts the apply
  errors while loggers keep writing to their previous output.

- File paths are normalized with filepath.Clean before deduplication, so
  the same file spelled differently (./x.log vs x.log) resolves to a
  single writer instead of two writers corrupting rotation.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
File paths in the destination list pass through filepath.Clean, whose
separator differs by OS. The ordering/dedupe test hardcoded the Unix form
and failed on Windows (\var\log\a.log vs /var/log/a.log); compare
against the cleaned form so the assertion is platform-correct.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
@JoshVanL
JoshVanL merged commit 0bec1a0 into dapr:main Sep 3, 2026
6 checks passed
nelson-parente added a commit to nelson-parente/kit that referenced this pull request Sep 3, 2026
…apr#166)

* feat: add --log-file-tee to write logs to both file and console

When --log-file is set the logger currently replaces the console writer,
so logs stop appearing in kubectl logs. --log-file-tee keeps both
destinations via io.MultiWriter.

The console writer is listed first because io.MultiWriter stops at the
first failed writer: this way console output survives file write failures
such as a full disk.

Default is false, so existing behaviour is unchanged.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* feat: add size/age-based rotation for --log-file output

--log-file opens the file in append mode and never rolls it, so long-lived
components grow it without bound. This adds rotation via lumberjack behind
four new flags:

  --log-file-max-size      megabytes before rotation
  --log-file-max-backups   rotated files to keep
  --log-file-max-age       days to retain rotated files
  --log-file-compress      gzip rotated files

When none of them is set the writer stays a plain append-mode *os.File, so
existing behaviour is byte-for-byte unchanged.

The three numeric options are string-typed and parsed at apply time so that
AttachCmdFlags keeps its (stringVar, boolVar) signature. That keeps the kit
bump non-breaking and surfaces the flags on every binary with no caller
changes.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* fix(test): call t.TempDir before registering the tee cleanup

t.Cleanup runs LIFO. The tee tests registered their cleanup before calling
t.TempDir, so TempDir's RemoveAll ran first — while the log file was still
open — and Windows cannot delete an open file:

  TempDir RemoveAll cleanup: unlinkat ...\dapr.log: The process cannot
  access the file because it is being used by another process.

Calling t.TempDir first registers its cleanup first, so it runs last, after
the cleanup that closes the file. The rotation test already had this
ordering, which is why only the two tee tests failed.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* test: cover rotation behaviour, flag registration and tee+rotation

The existing rotation tests asserted only that the lumberjack struct was
populated correctly. That would still pass if the rotating writer were never
installed as the log output, or if MaxSize were interpreted in the wrong
unit, so it verified configuration rather than behaviour.

Adds three gaps:

- TestFileRotationActuallyRotates drives ~3MB through the configured logger
  with max-size=1MB and asserts on disk that an archive appeared and the
  active file was rolled. Only size-based rotation is asserted: lumberjack
  rotates synchronously on the write that exceeds MaxSize, whereas
  compression and MaxBackups pruning run on a background goroutine and would
  make the assertion timing-dependent.

- TestOptions/registers_the_exact_set_of_log_flags asserts the full
  registered flag set rather than spot-checking names. Flag names become D3E
  chart annotations, so a rename is a breaking change for anyone who has
  already set them; this makes that fail deliberately rather than silently.

- TestTeeWithRotation covers file output that both rotates and tees, which is
  the combination actually configured in the field. Nothing previously
  exercised the MultiWriter-wrapping-lumberjack composition.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* feat: warn on inert file options; keep file permissions stable under rotation

Two behaviours surfaced by review:

1. Setting --log-file-tee or any rotation flag without --log-file was a
   silent no-op. Now it logs a warning through a dapr.kit.logger logger.
   Warn rather than fail: an error here would turn a harmless
   misconfiguration into a startup failure for every binary that attaches
   these flags. The logger is fetched at the top of ApplyOptionsToLoggers,
   before the registry snapshot, so it always follows the configured
   format, level and output for that apply.

2. lumberjack creates missing log files as 0600 (and preserves the mode of
   existing ones), where the non-rotating path creates 0644. Enabling
   rotation would therefore silently change new log file permissions and
   break log shippers tailing the file as a non-owner user. newFileWriter
   now pre-creates the file with the same flags and mode as the plain
   path, so permissions are identical whether or not rotation is enabled;
   rotated archives and post-rotation files inherit that mode.

The permission test compares the plain and rotating paths against each
other rather than asserting an absolute mode, so it is immune to umask
and Windows permission semantics.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* refactor: parse rotation values with ParseUint

Review feedback: the rotation values are semantically unsigned, so parse
them with strconv.ParseUint (bit size 31 keeps the int conversion safe on
32-bit platforms) instead of Atoi plus a sign check. The struct fields stay
string-typed because they bind through AttachCmdFlags(stringVar, boolVar);
widening that signature would break every existing caller.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* test: include log-timestamp-format in the exact flag set

Rebase onto main after dapr#167 merged: the exact-flag-set test failed as
designed on the new flag; register it in the expected set.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* refactor: typed optional fields behind separate flag receivers; compression enum

Review feedback: the optional fields are now properly typed and unexported —
*uint rotation limits (nil = not provided) and a none|gzip compression
enum — with the CLI flags attached to separate unexported string receivers
that validate() parses at apply time, mirroring the pattern in dapr/dapr
cmd/daprd/options. AttachCmdFlags keeps its (stringVar, boolVar) signature,
so no caller changes anywhere.

--log-file-compress (bool) becomes --log-file-compression=none|gzip, which
leaves room for other codecs without a flag break. Invalid values fail
validation before any logger is mutated.

Behaviour is unchanged: an explicit 0 still disables the corresponding
limit, and with no rotation engaged the writer stays the plain append-mode
file.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* feat: replace --log-file-tee with a --log-outputs destination list

Review feedback: rather than a tee bool, log destinations are a list.
--log-outputs takes a comma-separated list of "stdout", "stderr", or file
paths; --log-file merges into that list, so both flags compose and existing
behaviour is unchanged. The previous tee semantics are expressed as
--log-outputs=stdout,/path/to/file.

Destinations are deduplicated (two writers on one path would double every
line and corrupt rotation) and console destinations order before files, so
io.MultiWriter keeps console output alive when file writes start failing.
Rotation and compression options apply to every file destination.

The inert-option warning now keys on "no file destination configured"
rather than --log-file specifically.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* fix: align rotation usage strings with the destination model; test the open-failure unwind

Pre-merge review findings:

- The three rotation usage strings still said "No effect without
  --log-file", but the actual gating (and the warning) treat a file entry
  in --log-outputs as an equally valid destination. All four file-option
  usage strings now say "a file destination".

- The mid-list open-failure unwind in setLogOutput (close already-opened
  files, leave every logger on its previous output) had no test. Added one
  that applies destinations [stdout, <directory>] and asserts the apply
  errors while loggers keep writing to their previous output.

- File paths are normalized with filepath.Clean before deduplication, so
  the same file spelled differently (./x.log vs x.log) resolves to a
  single writer instead of two writers corrupting rotation.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* fix(test): compare destinations against the filepath.Clean form

File paths in the destination list pass through filepath.Clean, whose
separator differs by OS. The ordering/dedupe test hardcoded the Unix form
and failed on Windows (\var\log\a.log vs /var/log/a.log); compare
against the cleaned form so the assertion is platform-correct.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

---------

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
nelson-parente added a commit to nelson-parente/kit that referenced this pull request Sep 3, 2026
…apr#166)

* feat: add --log-file-tee to write logs to both file and console

When --log-file is set the logger currently replaces the console writer,
so logs stop appearing in kubectl logs. --log-file-tee keeps both
destinations via io.MultiWriter.

The console writer is listed first because io.MultiWriter stops at the
first failed writer: this way console output survives file write failures
such as a full disk.

Default is false, so existing behaviour is unchanged.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* feat: add size/age-based rotation for --log-file output

--log-file opens the file in append mode and never rolls it, so long-lived
components grow it without bound. This adds rotation via lumberjack behind
four new flags:

  --log-file-max-size      megabytes before rotation
  --log-file-max-backups   rotated files to keep
  --log-file-max-age       days to retain rotated files
  --log-file-compress      gzip rotated files

When none of them is set the writer stays a plain append-mode *os.File, so
existing behaviour is byte-for-byte unchanged.

The three numeric options are string-typed and parsed at apply time so that
AttachCmdFlags keeps its (stringVar, boolVar) signature. That keeps the kit
bump non-breaking and surfaces the flags on every binary with no caller
changes.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* fix(test): call t.TempDir before registering the tee cleanup

t.Cleanup runs LIFO. The tee tests registered their cleanup before calling
t.TempDir, so TempDir's RemoveAll ran first — while the log file was still
open — and Windows cannot delete an open file:

  TempDir RemoveAll cleanup: unlinkat ...\dapr.log: The process cannot
  access the file because it is being used by another process.

Calling t.TempDir first registers its cleanup first, so it runs last, after
the cleanup that closes the file. The rotation test already had this
ordering, which is why only the two tee tests failed.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* test: cover rotation behaviour, flag registration and tee+rotation

The existing rotation tests asserted only that the lumberjack struct was
populated correctly. That would still pass if the rotating writer were never
installed as the log output, or if MaxSize were interpreted in the wrong
unit, so it verified configuration rather than behaviour.

Adds three gaps:

- TestFileRotationActuallyRotates drives ~3MB through the configured logger
  with max-size=1MB and asserts on disk that an archive appeared and the
  active file was rolled. Only size-based rotation is asserted: lumberjack
  rotates synchronously on the write that exceeds MaxSize, whereas
  compression and MaxBackups pruning run on a background goroutine and would
  make the assertion timing-dependent.

- TestOptions/registers_the_exact_set_of_log_flags asserts the full
  registered flag set rather than spot-checking names. Flag names become D3E
  chart annotations, so a rename is a breaking change for anyone who has
  already set them; this makes that fail deliberately rather than silently.

- TestTeeWithRotation covers file output that both rotates and tees, which is
  the combination actually configured in the field. Nothing previously
  exercised the MultiWriter-wrapping-lumberjack composition.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* feat: warn on inert file options; keep file permissions stable under rotation

Two behaviours surfaced by review:

1. Setting --log-file-tee or any rotation flag without --log-file was a
   silent no-op. Now it logs a warning through a dapr.kit.logger logger.
   Warn rather than fail: an error here would turn a harmless
   misconfiguration into a startup failure for every binary that attaches
   these flags. The logger is fetched at the top of ApplyOptionsToLoggers,
   before the registry snapshot, so it always follows the configured
   format, level and output for that apply.

2. lumberjack creates missing log files as 0600 (and preserves the mode of
   existing ones), where the non-rotating path creates 0644. Enabling
   rotation would therefore silently change new log file permissions and
   break log shippers tailing the file as a non-owner user. newFileWriter
   now pre-creates the file with the same flags and mode as the plain
   path, so permissions are identical whether or not rotation is enabled;
   rotated archives and post-rotation files inherit that mode.

The permission test compares the plain and rotating paths against each
other rather than asserting an absolute mode, so it is immune to umask
and Windows permission semantics.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* refactor: parse rotation values with ParseUint

Review feedback: the rotation values are semantically unsigned, so parse
them with strconv.ParseUint (bit size 31 keeps the int conversion safe on
32-bit platforms) instead of Atoi plus a sign check. The struct fields stay
string-typed because they bind through AttachCmdFlags(stringVar, boolVar);
widening that signature would break every existing caller.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* test: include log-timestamp-format in the exact flag set

Rebase onto main after dapr#167 merged: the exact-flag-set test failed as
designed on the new flag; register it in the expected set.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* refactor: typed optional fields behind separate flag receivers; compression enum

Review feedback: the optional fields are now properly typed and unexported —
*uint rotation limits (nil = not provided) and a none|gzip compression
enum — with the CLI flags attached to separate unexported string receivers
that validate() parses at apply time, mirroring the pattern in dapr/dapr
cmd/daprd/options. AttachCmdFlags keeps its (stringVar, boolVar) signature,
so no caller changes anywhere.

--log-file-compress (bool) becomes --log-file-compression=none|gzip, which
leaves room for other codecs without a flag break. Invalid values fail
validation before any logger is mutated.

Behaviour is unchanged: an explicit 0 still disables the corresponding
limit, and with no rotation engaged the writer stays the plain append-mode
file.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* feat: replace --log-file-tee with a --log-outputs destination list

Review feedback: rather than a tee bool, log destinations are a list.
--log-outputs takes a comma-separated list of "stdout", "stderr", or file
paths; --log-file merges into that list, so both flags compose and existing
behaviour is unchanged. The previous tee semantics are expressed as
--log-outputs=stdout,/path/to/file.

Destinations are deduplicated (two writers on one path would double every
line and corrupt rotation) and console destinations order before files, so
io.MultiWriter keeps console output alive when file writes start failing.
Rotation and compression options apply to every file destination.

The inert-option warning now keys on "no file destination configured"
rather than --log-file specifically.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* fix: align rotation usage strings with the destination model; test the open-failure unwind

Pre-merge review findings:

- The three rotation usage strings still said "No effect without
  --log-file", but the actual gating (and the warning) treat a file entry
  in --log-outputs as an equally valid destination. All four file-option
  usage strings now say "a file destination".

- The mid-list open-failure unwind in setLogOutput (close already-opened
  files, leave every logger on its previous output) had no test. Added one
  that applies destinations [stdout, <directory>] and asserts the apply
  errors while loggers keep writing to their previous output.

- File paths are normalized with filepath.Clean before deduplication, so
  the same file spelled differently (./x.log vs x.log) resolves to a
  single writer instead of two writers corrupting rotation.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

* fix(test): compare destinations against the filepath.Clean form

File paths in the destination list pass through filepath.Clean, whose
separator differs by OS. The ordering/dedupe test hardcoded the Unix form
and failed on Windows (\var\log\a.log vs /var/log/a.log); compare
against the cleaned form so the assertion is platform-correct.

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>

---------

Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants