Skip to content

Conversation

@cjcocokrisp
Copy link
Collaborator

@cjcocokrisp cjcocokrisp commented Jan 19, 2026

What does this PR do / why we need it:

This PR addresses #459. This issue states that some subsystems are a bit too noisy and it would be nice to have configurable log levels for the subsystems. I accomplished this by adjusting the centralized logging class to now have a struct called CentralizedLogger which has the same functionality as before just with a replaceable logrus.Logger.

I made adjustments to the areas of code that used the default one and then for the subsystems I replaced the logCtx assignments to be below.

Before:

logCtx := log().WithFields(logrus.Fields{
		"method":         "processIncomingRedisRequest",
		"uuid":           rreq.UUID,
		"connectionUUID": rreq.ConnectionUUID,
})

After:

logCtx := a.redisProxyLogger.ModuleLogger("Agent").WithFields(logrus.Fields{
		"method":         "processIncomingRedisRequest",
		"uuid":           rreq.UUID,
		"connectionUUID": rreq.ConnectionUUID,
})

I do have one issue with this however that I would like some feedback on. To me it seems long and also the name of the module is hard coded (this can easily be fixed by having a package level constant for the name of that or it could go in the log fields not sure which would be better gut is saying the package level constant). The object calling like so also seems a bit more to me. If there are any suggestions on a better way to accomplish this it would be much appreciated.

I updated the logging docs to reflect the changes I made and updated the Kubernetes manifests to include the new env variables. Let me know if anything is missing or should be done differently.

(Also my lsp was doing some formatting fixes removing some blank lines, adding separation lines in markdown files, so I apologize for a lot of the lines being that)

Which issue(s) this PR fixes:

Fixes #459

How to test changes / Special notes to the reviewer:

See new unit tests added for the CentralizedLogger.
Run the agent with these changes

Checklist

  • Documentation update is required by this PR (and has been updated) OR no documentation update is required.

Summary by CodeRabbit

  • New Features

    • Per-subsystem centralized logging with independent loggers for Resource Proxy, Redis Proxy, and gRPC Event, with sensible defaults.
    • CLI log-level flag accepts multiple entries and component-specific overrides using [=].
    • New options to inject subsystem loggers into servers, proxies, and components.
  • Documentation

    • Updated agent/principal logging docs and configuration references for per-component log levels and format.
  • Tests

    • Added tests validating parsing and behavior of subsystem log level configurations.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

Adds a CentralizedLogger instance API and per-subsystem loggers (resource-proxy, redis-proxy, grpc-event); converts package-level logging helpers into CentralizedLogger methods; adds CLI parsing for subsystem log levels; wires subsystem loggers through agent/principal options and replaces global log() calls with subsystem- or instance-specific loggers.

Changes

Cohort / File(s) Summary
Core logging library
internal/logging/logging.go, internal/logging/logging_test.go, internal/logging/README.md, internal/logging/examples/migration_examples.go
Introduced CentralizedLogger wrapper, New(...), GetDefaultLogger() and converted package-level logger helpers into methods on CentralizedLogger; updated tests/docs/examples to use instance-based API.
CLI log helpers & parsing
cmd/cmdutil/log.go, cmd/cmdutil/log_test.go
Added CreateLogger, SubSystemLoggers, AvailableSubSystems, and ParseLogLevels to create per-subsystem loggers and parse [component=]level inputs; added tests for parsing behavior.
Agent: options & wiring
agent/agent.go, agent/options.go, cmd/argocd-agent/agent.go, agent/inbound.go, agent/inbound_redis.go, agent/outbound.go, agent/resource.go, agent/resource_test.go
Added resourceProxyLogger, redisProxyLogger, grpcEventLogger fields and WithSubsystemLoggers option; CLI flag changed to string-slice; replaced global log() calls with subsystem-specific a.logResourceProxy(), a.logRedisProxy(), a.logGrpcEvent() across agent code.
Principal: options & wiring
principal/options.go, principal/server.go, principal/event.go, principal/listen.go, principal/logging.go
Added subsystem logger fields and WithSubsystemLoggers option; default nil loggers to GetDefaultLogger(); introduced logGrpcEvent() helper on Server; eventstream.NewServer now accepts variadic ServerOption including WithLogger.
Resource & Redis proxies
principal/resourceproxy/resourceproxy.go, principal/resourceproxy/options.go, principal/resourceproxy/tracking.go, principal/resourceproxy/resourceproxy_test.go, principal/redisproxy/redisproxy.go, principal/redisproxy/redisproxy_test.go
Added per-instance logger *logging.CentralizedLogger, WithLogger options, updated constructors/signatures to accept loggers, and replaced package-level log() usages with per-instance rp.log()/rp.logger.ModuleLogger(...).
Event & gRPC paths
internal/event/event.go, principal/apis/eventstream/eventstream.go, principal/apis/eventstream/eventstream_test.go, principal/event.go
Event writer/server and event handlers now obtain loggers via centralized accessors; replaced global log() with grpc-event subsystem logger (s.logGrpcEvent() / instance log methods) in processing paths and tests.
Many package-level logger replacements
e.g. internal/auth/*, internal/backend/..., internal/cache/*, pkg/client/remote.go, principal/apis/auth/auth.go, internal/manager/*, internal/event/*, principal/tracker/*
Replaced direct package-level logger calls with logging.GetDefaultLogger() instance methods (ModuleLogger/ComponentLogger) across multiple packages; minor formatting/type grouping changes.
Docs & config
docs/*, docs/configuration/reference/agent.md, docs/configuration/reference/principal.md
Documented subsystem log-level format and available components; updated migration docs and examples to reflect CentralizedLogger and subsystem log level format.
Tests
cmd/cmdutil/log_test.go, various updated tests under principal/, agent/
Added/updated tests to validate ParseLogLevels and to use CentralizedLogger-based logger initialization; test logic unchanged aside from logger wiring.

Sequence Diagram(s)

(Skipped)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • jannfis
  • jgwest
  • chetan-rns
  • mikeshng

Poem

🐰
I hopped through logs and stitched each little stream,
Resource, Redis, gRPC — each with its own theme.
Central ears listen, subsystems softly chime,
Less noisy nights and clearer daytime,
A nimble hop, and logging feels like a dream.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(logs): toggle logging for subsystems' accurately describes the primary change: implementing per-subsystem configurable logging, which is the main objective of the PR.
Linked Issues check ✅ Passed The PR successfully implements the core requirements from issue #459: per-subsystem log-level configuration [#459], comma-separated CLI flag format with component-qualified values [#459], and subsystem-specific loggers for resource-proxy, redis-proxy, and grpc-event [#459].
Out of Scope Changes check ✅ Passed All changes are scoped to logging infrastructure. The PR introduces CentralizedLogger, subsystem logger injection, CLI flag parsing, and documentation updates—all directly supporting the toggling of subsystem logging. No unrelated functional changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
principal/redisproxy/redisproxy.go (2)

89-112: Inconsistent logging: Start() still uses global log() instead of rp.logger.

The Start() method uses the global log() function (lines 92, 102, 109) rather than the instance logger rp.logger.ModuleLogger("redisProxy"). This defeats the purpose of per-subsystem log level control for these log statements.

🔧 Proposed fix
 func (rp *RedisProxy) Start() error {
+	logCtx := rp.logger.ModuleLogger("redisProxy")
 	l, err := net.Listen("tcp", rp.listenAddress)
 	if err != nil {
-		log().WithError(err).Error("error occurred on listening to addr: " + rp.listenAddress)
+		logCtx.WithError(err).Error("error occurred on listening to addr: " + rp.listenAddress)
 		return err
 	}
 	rp.listener = l

 	// Start server and connection handler
 	go func() {
 		for {
 			conn, err := l.Accept()
 			if err != nil {
-				log().WithError(err).Error("error occurred on accepting connection")
+				logCtx.WithError(err).Error("error occurred on accepting connection")
 				return
 			}
 			go rp.handleConnection(conn)
 		}
 	}()

-	log().Infof("Redis proxy started on %s", rp.listenAddress)
+	logCtx.Infof("Redis proxy started on %s", rp.listenAddress)

 	return nil
 }

708-725: Inconsistent logging in writeToArgoCDRedisSocket.

Line 715 uses log().WithError(err).Error(...) instead of the instance logger or the provided logCtx. For consistency with the subsystem logging approach, this should use the logger context.

🔧 Proposed fix
 func (are *argoCDRedisWriterInternal) writeToArgoCDRedisSocket(logCtx *logrus.Entry, bytes []byte) error {
 	are.argoCDWriteLock.Lock()
 	defer are.argoCDWriteLock.Unlock()

 	_, err := are.fromArgoCDWrite.Write(bytes)
 	if err != nil {
-		log().WithError(err).Error("unable to write to Argo CD Redis socket")
+		logCtx.WithError(err).Error("unable to write to Argo CD Redis socket")
 		return err
 	}

 	if err := are.fromArgoCDWrite.Flush(); err != nil {
 		logCtx.WithError(err).Info("unable to flush to Argo CD Redis socket")
 		return err
 	}

 	return nil
 }
principal/apis/eventstream/eventstream.go (1)

102-114: Potential nil pointer dereference if WithLogger option is not provided.

NewServer does not set a default logger, but Push() at line 425 directly accesses s.options.logger.ModuleLogger(...). If the caller does not use WithLogger, this will panic.

Consider setting a default logger:

 func NewServer(queues queue.QueuePair, eventWriters *event.EventWritersMap, metrics *metrics.PrincipalMetrics, clusterMgr *cluster.Manager, opts ...ServerOption) *Server {
 	options := &ServerOptions{}
 	for _, o := range opts {
 		o(options)
 	}
+	if options.logger == nil {
+		options.logger = logging.GetDefaultLogger()
+	}
 	return &Server{
 		queues:       queues,
 		options:      options,
 		eventWriters: eventWriters,
 		metrics:      metrics,
 		clusterMgr:   clusterMgr,
 	}
 }
🤖 Fix all issues with AI agents
In `@agent/agent.go`:
- Around line 119-124: Typo in the comment above the logger fields: change
"contorl" to "control" to correct the comment describing subsystem loggers;
update the comment that sits above the fields resourceProxyLogger,
redisProxyLogger, and grpcEventLogger in the agent struct (in agent.go) to read
"control" so the documentation is accurate.

In `@cmd/argocd-agent/agent.go`:
- Around line 142-148: Rename the misspelled variable resorceProxyLogger to
resourceProxyLogger wherever it's declared and used so the call to
agent.WithSubsystemLoggers(resourceProxyLogger, redisProxyLogger,
grpcEventLogger) passes the correctly named logger; update the declaration that
calls cmdutil.CreateLogger(logLevelResourceProxy, logFormat) and any references
to resorceProxyLogger to the new name to keep identifiers consistent.

In `@cmd/argocd-agent/principal.go`:
- Around line 158-162: The WithSubsystemLoggers implementation incorrectly
assigns the fallback logger to o.options.resourceProxyLogger when grpcEvent is
nil; update the nil-check branch in WithSubsystemLoggers so that when grpcEvent
is nil you create the default logger (using the same creation pattern as the
other fallbacks) and assign it to o.options.grpcEventLogger (not
o.options.resourceProxyLogger), ensuring resourceProxyLogger is not overwritten
and grpcEventLogger is properly initialized.

In `@cmd/cmdutil/log.go`:
- Around line 95-104: The condition checking logLevel != "" is redundant because
logLevel is set to "info" when empty; remove that outer if and always parse the
logLevel using StringToLoglevel(logLevel). Replace the misleading Fatal message
string ("invalid resource proxy log level") with a generic message (e.g.,
"invalid log level") that includes the supplied logLevel and the
AvailableLogLevels() output, and keep calling logger.SetLevel(lvl) after
successful parsing; update references in this block involving the variables
logLevel, StringToLoglevel, AvailableLogLevels, Fatal, and logger.SetLevel.
- Around line 93-130: The CreateLogger function currently adds writer.Hook hooks
but doesn't discard the default logger output, causing duplicate log entries;
update CreateLogger (the function that constructs and returns *logrus.Logger) to
call logger.SetOutput(io.Discard) before adding the two writer.Hook hooks so all
output is routed only through the hooks, and ensure you import io if not already
imported.

In `@docs/configuration/agent/configuration.md`:
- Around line 204-222: The headings for the new subsystem log options "Redis
Proxy Log Level" and "GRPC Event Log Level" are using ### and break the document
hierarchy; change those two headings to #### to match "Resource Proxy Log Level"
and the rest of the file so section depth is consistent, leaving all other
content (flags, env vars, config entries, descriptions, types, defaults, valid
values, examples) unchanged.

In `@docs/configuration/principal/configuration.md`:
- Around line 367-396: The headings "Redis Proxy Log Level" and "GRPC Event Log
Level" are using ### but should be #### to match "Resource Proxy Log Level" and
maintain hierarchy under "Logging and Debugging"; update the markdown headings
for the sections titled "Redis Proxy Log Level" and "GRPC Event Log Level" to
use four hashes (####) instead of three so all three proxy log level subsections
("Resource Proxy Log Level", "Redis Proxy Log Level", "GRPC Event Log Level")
are at the same level.

In `@install/helm-repo/argocd-agent-agent/templates/agent-deployment.yaml`:
- Around line 46-63: The deployment manifest uses incorrect env var names that
don't match the agent code; update the environment variable keys so they match
the names used in cmd/argocd-agent/agent.go: replace
ARGOCD_AGENT_LOG_REDIS_PROXY_LEVEL with ARGOCD_AGENT_REDIS_PROXY_LOG_LEVEL and
replace ARGOCD_AGENT_LOG_GRPC_LOG_LEVEL with ARGOCD_AGENT_GRPC_LOG_LEVEL in the
container env block (the other name ARGOCD_AGENT_RESOURCE_PROXY_LOG_LEVEL is
already correct).

In `@install/helm-repo/argocd-agent-agent/templates/tests/test-configMap.yaml`:
- Around line 78-103: The test script contains copy-paste typos: correct the
three comment lines that currently read "Test agent.log.level" to describe each
specific file (resource-proxy, redis-proxy, grpc), fix the stray "level" suffix
in the cat invocation for agent.log.resource-proxy-level (remove the duplicated
"level"), and correct the misspelled error message "resrouce" to "resource" so
the existence checks and error outputs reference the right keys:
agent.log.resource-proxy-level, agent.log.redis-proxy-level, and
agent.log.grpc-level.

In `@install/kubernetes/agent/agent-deployment.yaml`:
- Around line 42-59: The environment variable names are inconsistent:
ARGOCD_AGENT_RESOURCE_PROXY_LOG_LEVEL, ARGOCD_AGENT_LOG_REDIS_PROXY_LEVEL, and
ARGOCD_AGENT_LOG_GRPC_LOG_LEVEL should follow a single pattern; rename
ARGOCD_AGENT_LOG_REDIS_PROXY_LEVEL → ARGOCD_AGENT_REDIS_PROXY_LOG_LEVEL and
ARGOCD_AGENT_LOG_GRPC_LOG_LEVEL → ARGOCD_AGENT_GRPC_EVENT_LOG_LEVEL (or another
consistent *_<component>_LOG_LEVEL form) and update their corresponding
configMapKeyRef keys (e.g., map agent.log.redis-proxy-level and
agent.log.grpc-level to the new variable names) and any code/templates that
consume these env vars (look for usages of the old symbols to update).

In `@install/kubernetes/principal/principal-deployment.yaml`:
- Around line 42-59: The environment variable names are inconsistent: change
ARGOCD_PRINCIPAL_RESOURCE_PROXY_LOG_LEVEL,
ARGOCD_PRINCIPAL_LOG_REDIS_PROXY_LEVEL, and ARGOCD_PRINCIPAL_LOG_GRPC_LOG_LEVEL
to follow the existing ARGOCD_PRINCIPAL_LOG_LEVEL naming pattern (e.g.,
ARGOCD_PRINCIPAL_LOG_RESOURCE_PROXY_LEVEL,
ARGOCD_PRINCIPAL_LOG_REDIS_PROXY_LEVEL, and ARGOCD_PRINCIPAL_LOG_GRPC_LEVEL) so
all subsystem log level vars use the common
ARGOCD_PRINCIPAL_LOG_<SUBSYSTEM>_LEVEL convention; update the Deployment env var
names accordingly wherever those keys are consumed or referenced.

In `@install/kubernetes/principal/principal-params-cm.yaml`:
- Around line 10-11: The comment for the principal.listen.port key is
corrupted/merged; update the inline comment above or next to
principal.listen.port to a clear, complete sentence such as
"principal.listen.port: The port the gRPC server should listen on; Default:
8443" so operators understand purpose and default; ensure the comment text is
properly separated from the key (e.g., using a leading # and whitespace) and
keep the existing value principal.listen.port: "8443" unchanged.
- Around line 16-26: The comments for the log level keys
(principal.log.resource-proxy-level, principal.log.redis-proxy-level,
principal.log.grpc-level) incorrectly refer to "the agent"; update each comment
to say "the principal" (or "principal" subsystem) instead so the description
matches the keys' namespace—keep the rest of the text and valid values
unchanged.

In `@internal/logging/logging.go`:
- Around line 233-238: The ErrorLogger method on CentralizedLogger calls
err.Error() unguarded which will panic if err is nil; update
CentralizedLogger.ErrorLogger to check whether the err parameter is nil and only
call err.Error() when non-nil (otherwise pass an empty string or a sentinel like
"<nil>") into the logrus.Fields value for logfields.Error so logging remains
safe when callers provide a nil error.

In `@internal/logging/README.md`:
- Around line 133-135: The example uses the removed package-level function
logging.ErrorLogger; update it to call the ErrorLogger method on the default
logger instead (e.g. replace logging.ErrorLogger("ModuleName",
"ConnectionError", err) with logging.Default().ErrorLogger("ModuleName",
"ConnectionError", err)), then use the returned logger instance's Error method
(logger.Error("Error occurred")) so the snippet compiles with the instance-based
API.

In `@principal/options.go`:
- Around line 525-529: The conditional assigns the wrong field when grpcEvent is
nil, leaving o.options.grpcEventLogger unset; change the else branch to set
o.options.grpcEventLogger = logging.GetDefaultLogger() instead of
o.options.resourceProxyLogger so that the grpc event logger is always
initialized (update the block that touches o.options.grpcEventLogger and
o.options.resourceProxyLogger accordingly).

In `@principal/redisproxy/redisproxy.go`:
- Line 245: The inline comment for the variable
forwardRawCommandToPrincipalRedis contains a typo ("falsGetDefaultLogger().e") —
update the comment to read "false" so it correctly explains that the flag is
false when the command has already been processed and true otherwise; locate the
declaration of forwardRawCommandToPrincipalRedis in redisproxy.go and correct
the comment text accordingly.

In `@principal/resourceproxy/options.go`:
- Around line 48-55: WithLogger is incorrectly declared as a method on
*ResourceProxy (receiver rp) but should be a standalone function like
WithTLSConfig and WithRequestMatcher so it can be used as a functional option
during New(); remove the receiver and change the signature to func
WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption, leaving the
body to set p.logger and return nil, and ensure the return type uses the
existing ResourceProxyOption type so callers of New() can pass WithLogger(...)
just like the other option functions.
🧹 Nitpick comments (6)
internal/auth/header/header.go (1)

31-31: Consider lazy logger access to avoid stale entries.
With a replaceable default logger, caching a *logrus.Entry at init time can bypass later logger swaps. Prefer a log() helper that calls GetDefaultLogger() per use, matching other packages.

install/helm-repo/argocd-agent-agent/values.schema.json (1)

133-144: Consider adding enum constraints for log level validation.

The existing logFormat property uses an enum constraint for validation. For consistency and to prevent invalid configuration values at deploy time, consider adding enum constraints to these new log level properties. Also, the description mentions "debug, info, warn, error" but the codebase also supports "trace" and "fatal" levels.

Suggested improvement
     "resourceProxyLogLevel": {
       "type": "string",
-      "description": "Log level (debug, info, warn, error)"
+      "description": "Log level for resource proxy subsystem",
+      "enum": ["trace", "debug", "info", "warn", "error", "fatal"]
     },
     "redisProxyLogLevel": {
       "type": "string",
-      "description": "Log level (debug, info, warn, error)"
+      "description": "Log level for Redis proxy subsystem",
+      "enum": ["trace", "debug", "info", "warn", "error", "fatal"]
     },
     "grpcEventLogLevel": {
       "type": "string",
-      "description": "Log level (debug, info, warn, error)"
+      "description": "Log level for gRPC events subsystem",
+      "enum": ["trace", "debug", "info", "warn", "error", "fatal"]
     },
agent/outbound.go (1)

191-195: Consider using logfields constants for consistency.

The Application event handlers use logfields.Event and logfields.Application constants, but the AppProject and Repository handlers use inline strings like "event" and "appProject". Consider using or adding constants to logfields for consistency across all event types.

agent/inbound_redis.go (1)

186-186: Log context fields are lost when reassigning logCtx.

The logCtx parameter passed into this function (containing "method", "uuid", "connectionUUID" fields) is discarded when reassigning to a fresh logger with only channelName. Consider using logCtx.WithField(...) instead to preserve the existing context:

-	logCtx = a.redisProxyLogger.ModuleLogger("Agent").WithField(logfields.ChannelName, channelName)
+	logCtx = logCtx.WithField(logfields.ChannelName, channelName)

This preserves debugging context (connection UUID, request UUID) in subscription-related logs.

principal/resourceproxy/resourceproxy.go (1)

189-189: Consider caching the module logger to reduce repeated ModuleLogger calls.

rp.logger.ModuleLogger("proxy") is called on every log statement in proxyHandler. While not a correctness issue, you could cache the module logger once in the struct during initialization for slight efficiency:

type ResourceProxy struct {
    // ...
    moduleLogger *logrus.Entry  // cached module logger
}

Then in New():

p.moduleLogger = p.logger.ModuleLogger("proxy")

This is optional since the overhead is minimal in practice.

Also applies to: 198-198, 210-210, 225-225, 232-232

internal/logging/logging_test.go (1)

416-416: Typo in test name (SeperateSeparate).

Minor clarity tweak; keeps test naming consistent.

✍️ Suggested rename
-func TestSeperateLoggerLevels(t *testing.T) {
+func TestSeparateLoggerLevels(t *testing.T) {

Comment on lines 93 to 130
func CreateLogger(logLevel string, logFormat string) *logrus.Logger {
logger := logrus.New()
if logLevel == "" {
logLevel = "info"
}
if logLevel != "" {
lvl, err := StringToLoglevel(logLevel)
if err != nil {
Fatal("invalid resource proxy log level: %s. Available levels are %s", logLevel, AvailableLogLevels())
}
logger.SetLevel(lvl)
}
if formatter, err := LogFormatter(logFormat); err != nil {
Fatal("%s", err.Error())
} else {
logger.SetFormatter(formatter)
}

logger.AddHook(&writer.Hook{
Writer: os.Stderr,
LogLevels: []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
},
})
logger.AddHook(&writer.Hook{
Writer: os.Stdout,
LogLevels: []logrus.Level{
logrus.InfoLevel,
logrus.DebugLevel,
logrus.TraceLevel,
},
})

return 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 | 🟠 Major

Missing SetOutput(io.Discard) may cause duplicate log output.

The InitLogging function (line 57) calls logrus.SetOutput(io.Discard) before adding hooks to prevent logs from being written to both the default output and the hooks. This new function adds the same hooks but doesn't discard the default output, which may result in duplicate log entries.

Proposed fix
 func CreateLogger(logLevel string, logFormat string) *logrus.Logger {
 	logger := logrus.New()
+	logger.SetOutput(io.Discard) // Send all logs to nowhere by default
 	if logLevel == "" {
 		logLevel = "info"
 	}
📝 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
func CreateLogger(logLevel string, logFormat string) *logrus.Logger {
logger := logrus.New()
if logLevel == "" {
logLevel = "info"
}
if logLevel != "" {
lvl, err := StringToLoglevel(logLevel)
if err != nil {
Fatal("invalid resource proxy log level: %s. Available levels are %s", logLevel, AvailableLogLevels())
}
logger.SetLevel(lvl)
}
if formatter, err := LogFormatter(logFormat); err != nil {
Fatal("%s", err.Error())
} else {
logger.SetFormatter(formatter)
}
logger.AddHook(&writer.Hook{
Writer: os.Stderr,
LogLevels: []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
},
})
logger.AddHook(&writer.Hook{
Writer: os.Stdout,
LogLevels: []logrus.Level{
logrus.InfoLevel,
logrus.DebugLevel,
logrus.TraceLevel,
},
})
return logger
}
func CreateLogger(logLevel string, logFormat string) *logrus.Logger {
logger := logrus.New()
logger.SetOutput(io.Discard) // Send all logs to nowhere by default
if logLevel == "" {
logLevel = "info"
}
if logLevel != "" {
lvl, err := StringToLoglevel(logLevel)
if err != nil {
Fatal("invalid resource proxy log level: %s. Available levels are %s", logLevel, AvailableLogLevels())
}
logger.SetLevel(lvl)
}
if formatter, err := LogFormatter(logFormat); err != nil {
Fatal("%s", err.Error())
} else {
logger.SetFormatter(formatter)
}
logger.AddHook(&writer.Hook{
Writer: os.Stderr,
LogLevels: []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
},
})
logger.AddHook(&writer.Hook{
Writer: os.Stdout,
LogLevels: []logrus.Level{
logrus.InfoLevel,
logrus.DebugLevel,
logrus.TraceLevel,
},
})
return logger
}
🤖 Prompt for AI Agents
In `@cmd/cmdutil/log.go` around lines 93 - 130, The CreateLogger function
currently adds writer.Hook hooks but doesn't discard the default logger output,
causing duplicate log entries; update CreateLogger (the function that constructs
and returns *logrus.Logger) to call logger.SetOutput(io.Discard) before adding
the two writer.Hook hooks so all output is routed only through the hooks, and
ensure you import io if not already imported.

Comment on lines 16 to 26
# principal.log.resource-proxy-level: The log level the agent should use for the resource proxy.
# Valid values are trace, debug, info, warn and error.
# Default: "info"
principal.log.resource-proxy-level: "info"
# principal.log.redis-proxy-level: The log level the agent should use for the redis proxy.
# Valid values are trace, debug, info, warn and error.
# Default: "info"
principal.log.redis-proxy-level: "info"
# principal.log.grpc-level: The log level the agent should use for grpc events.
# Valid values are trace, debug, info, warn and error.
# Default: "info"
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 | 🟡 Minor

Use “principal” (not “agent”) in subsystem log comments.
These keys live under principal.*, so the current wording is misleading.

💡 Suggested fix
-  # principal.log.resource-proxy-level: The log level the agent should use for the resource proxy.
+  # principal.log.resource-proxy-level: The log level the principal should use for the resource proxy.
...
-  # principal.log.redis-proxy-level: The log level the agent should use for the redis proxy.
+  # principal.log.redis-proxy-level: The log level the principal should use for the redis proxy.
...
-  # principal.log.grpc-level: The log level the agent should use for grpc events.
+  # principal.log.grpc-level: The log level the principal should use for gRPC events.
🤖 Prompt for AI Agents
In `@install/kubernetes/principal/principal-params-cm.yaml` around lines 16 - 26,
The comments for the log level keys (principal.log.resource-proxy-level,
principal.log.redis-proxy-level, principal.log.grpc-level) incorrectly refer to
"the agent"; update each comment to say "the principal" (or "principal"
subsystem) instead so the description matches the keys' namespace—keep the rest
of the text and valid values unchanged.

Comment on lines +233 to 238
func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
return l.logger.WithFields(logrus.Fields{
logfields.Module: module,
logfields.ErrorType: errorType,
logfields.Error: err.Error(),
})
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 | 🟠 Major

Guard against nil error to avoid panic in ErrorLogger.

err.Error() will panic if err is nil. Logging should be safe even when callers don’t have an error instance.

🐛 Suggested fix
 func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
-	return l.logger.WithFields(logrus.Fields{
-		logfields.Module:    module,
-		logfields.ErrorType: errorType,
-		logfields.Error:     err.Error(),
-	})
+	fields := logrus.Fields{
+		logfields.Module:    module,
+		logfields.ErrorType: errorType,
+	}
+	if err != nil {
+		fields[logfields.Error] = err.Error()
+	}
+	return l.logger.WithFields(fields)
 }
📝 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
func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
return l.logger.WithFields(logrus.Fields{
logfields.Module: module,
logfields.ErrorType: errorType,
logfields.Error: err.Error(),
})
func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
fields := logrus.Fields{
logfields.Module: module,
logfields.ErrorType: errorType,
}
if err != nil {
fields[logfields.Error] = err.Error()
}
return l.logger.WithFields(fields)
}
🤖 Prompt for AI Agents
In `@internal/logging/logging.go` around lines 233 - 238, The ErrorLogger method
on CentralizedLogger calls err.Error() unguarded which will panic if err is nil;
update CentralizedLogger.ErrorLogger to check whether the err parameter is nil
and only call err.Error() when non-nil (otherwise pass an empty string or a
sentinel like "<nil>") into the logrus.Fields value for logfields.Error so
logging remains safe when callers provide a nil error.

parsedReceived := parsedRedisCommandVal.parsedReceived

forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: false if the command has already been processed, true otherwise.
forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: falsGetDefaultLogger().e if the command has already been processed, true otherwise.
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 | 🟡 Minor

Fix typo in comment.

The comment contains a corrupted word: falsGetDefaultLogger().e should be false.

📝 Proposed fix
-	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: falsGetDefaultLogger().e if the command has already been processed, true otherwise.
+	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: false if the command has already been processed, true otherwise.
🤖 Prompt for AI Agents
In `@principal/redisproxy/redisproxy.go` at line 245, The inline comment for the
variable forwardRawCommandToPrincipalRedis contains a typo
("falsGetDefaultLogger().e") — update the comment to read "false" so it
correctly explains that the flag is false when the command has already been
processed and true otherwise; locate the declaration of
forwardRawCommandToPrincipalRedis in redisproxy.go and correct the comment text
accordingly.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
principal/event.go (1)

791-798: Inconsistent logger usage in StartEventProcessor.

Line 794 still uses the old log() pattern while the rest of the event processing code uses s.options.grpcEventLogger. Consider updating for consistency.

✏️ Proposed fix
 func (s *Server) StartEventProcessor(ctx context.Context) error {
 	var err error
 	go func() {
-		log().Infof("Starting event processor")
+		s.options.grpcEventLogger.ModuleLogger("grpc").Info("Starting event processor")
 		err = s.eventProcessor(ctx)
 	}()
 	return err
 }
agent/resource.go (1)

47-51: Add default initialization for resourceProxyLogger to prevent nil pointer dereference.

The resourceProxyLogger is not initialized when NewAgent() is called without the WithSubsystemLoggers option. Since enableResourceProxy defaults to true, calling processIncomingResourceRequest() will panic at line 47 when invoking a.resourceProxyLogger.ModuleLogger() on a nil receiver.

Unlike the principal server (which provides logging.GetDefaultLogger() as a fallback in its WithSubsystemLoggers), the agent lacks this protection. Either provide a default logger in NewAgent() or ensure WithSubsystemLoggers is always called with non-nil arguments.

principal/apis/eventstream/eventstream.go (1)

101-114: Potential nil pointer dereference: missing default logger initialization.

Unlike ResourceProxy.New (lines 112-114 in resourceproxy.go) and NewServer in server.go (lines 216-226), this constructor doesn't default options.logger when WithLogger isn't called. This will cause a nil pointer dereference at line 425 when Push is invoked.

🔧 Proposed fix
 func NewServer(queues queue.QueuePair, eventWriters *event.EventWritersMap, metrics *metrics.PrincipalMetrics, clusterMgr *cluster.Manager, opts ...ServerOption) *Server {
 	options := &ServerOptions{}
 	for _, o := range opts {
 		o(options)
 	}
+	if options.logger == nil {
+		options.logger = logging.GetDefaultLogger()
+	}
 	return &Server{
 		queues:       queues,
 		options:      options,
 		eventWriters: eventWriters,
 		metrics:      metrics,
 		clusterMgr:   clusterMgr,
 	}
 }
principal/resourceproxy/resourceproxy.go (1)

119-131: Inconsistent logger usage for HTTP error logging.

Line 121 uses the log() helper function which always returns GetDefaultLogger().ModuleLogger("proxy"), bypassing the injected rp.logger. This means HTTP server errors won't respect a custom subsystem log level if one was configured.

🔧 Proposed fix
 	p.mux = http.NewServeMux()

 	// proxyHandler is the main HTTP handler, used to process every request
 	p.mux.HandleFunc("/", p.proxyHandler)

-	httpLogger := log().WriterLevel(logrus.ErrorLevel)
+	httpLogger := p.logger.ModuleLogger("proxy").WriterLevel(logrus.ErrorLevel)

 	// Configure the HTTP server
 	p.server = &http.Server{
principal/listen.go (1)

103-129: Use the actual listen error in the retry log.
Line 118 logs err, which is the outer ExponentialBackoff error and is likely nil during retries. Use lerr to preserve the root cause.

🐛 Proposed fix
-			s.options.grpcEventLogger.ModuleLogger("grpc").WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
+			s.options.grpcEventLogger.ModuleLogger("grpc").WithError(lerr).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
♻️ Duplicate comments (16)
docs/configuration/principal/configuration.md (1)

377-395: Fix subsection heading levels under “Logging and Debugging”.
The Redis Proxy Log Level and GRPC Event Log Level headings should use #### to match the Resource Proxy Log Level subsection hierarchy.

✏️ Proposed fix
-### Redis Proxy Log Level
+#### Redis Proxy Log Level
...
-### GRPC Event Log Level
+#### GRPC Event Log Level
install/kubernetes/principal/principal-params-cm.yaml (2)

10-11: Repair the corrupted listen-port comment.
The inline description is merged and unclear; split it for readability.

✏️ Proposed fix
-  # principal.listen.port: The port the gRPC server should listenprincipal  # Default: 8443
+  # principal.listen.port: The port the gRPC server should listen on.
+  # Default: 8443

16-26: Use “principal” in subsystem log level comments.
These keys live under principal.*, so the comments should reference the principal component.

✏️ Proposed fix
-  # principal.log.resource-proxy-level: The log level the agent should use for the resource proxy.
+  # principal.log.resource-proxy-level: The log level the principal should use for the resource proxy.
...
-  # principal.log.redis-proxy-level: The log level the agent should use for the redis proxy.
+  # principal.log.redis-proxy-level: The log level the principal should use for the redis proxy.
...
-  # principal.log.grpc-level: The log level the agent should use for grpc events.
+  # principal.log.grpc-level: The log level the principal should use for gRPC events.
principal/redisproxy/redisproxy.go (1)

245-245: Fix typo in inline comment.
The comment text is corrupted (“falsGetDefaultLogger().e”).

✏️ Proposed fix
-		forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: falsGetDefaultLogger().e if the command has already been processed, true otherwise.
+		forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: false if the command has already been processed, true otherwise.
internal/logging/logging.go (1)

233-238: Guard against nil error in ErrorLogger to prevent panic.
err.Error() will panic when err is nil.

🐛 Proposed fix
 func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
-	return l.logger.WithFields(logrus.Fields{
-		logfields.Module:    module,
-		logfields.ErrorType: errorType,
-		logfields.Error:     err.Error(),
-	})
+	fields := logrus.Fields{
+		logfields.Module:    module,
+		logfields.ErrorType: errorType,
+	}
+	if err != nil {
+		fields[logfields.Error] = err.Error()
+	}
+	return l.logger.WithFields(fields)
 }
cmd/cmdutil/log.go (2)

111-127: Duplicate log output risk when using hooks.
Because the logger writes to its default output in addition to the stdout/stderr hooks, log lines can be duplicated unless the base output is discarded.

🛠️ Proposed fix
 func CreateLogger(logLevel string, logFormat string) *logrus.Logger {
 	logger := logrus.New()
+	logger.SetOutput(io.Discard) // prevent duplicate output when hooks are installed
 	if logLevel == "" {
 		logLevel = "info"
 	}

95-104: Redundant condition and misleading error message.
The logLevel != "" check is always true after defaulting, and the error text is subsystem-specific in a generic helper.

✏️ Proposed fix
 	if logLevel == "" {
 		logLevel = "info"
 	}
-	if logLevel != "" {
-		lvl, err := StringToLoglevel(logLevel)
-		if err != nil {
-			Fatal("invalid resource proxy log level: %s. Available levels are %s", logLevel, AvailableLogLevels())
-		}
-		logger.SetLevel(lvl)
-	}
+	lvl, err := StringToLoglevel(logLevel)
+	if err != nil {
+		Fatal("invalid log level: %s. Available levels are %s", logLevel, AvailableLogLevels())
+	}
+	logger.SetLevel(lvl)
agent/agent.go (1)

120-123: Typo in comment (“contorl”).

✏️ Proposed fix
-	// below are loggers to contorl log levels of different subsystems
+	// below are loggers to control log levels of different subsystems
cmd/argocd-agent/agent.go (1)

143-147: Fix typo in variable name: resorceProxyLoggerresourceProxyLogger.

The variable name has a typo - missing the letter 'u' in "resource".

✏️ Proposed fix
-		resorceProxyLogger := cmdutil.CreateLogger(logLevelResourceProxy, logFormat)
+		resourceProxyLogger := cmdutil.CreateLogger(logLevelResourceProxy, logFormat)
		redisProxyLogger := cmdutil.CreateLogger(logLevelRedisProxy, logFormat)
		grpcEventLogger := cmdutil.CreateLogger(logLevelGrpcEvent, logFormat)

-		agentOpts = append(agentOpts, agent.WithSubsystemLoggers(resorceProxyLogger, redisProxyLogger, grpcEventLogger))
+		agentOpts = append(agentOpts, agent.WithSubsystemLoggers(resourceProxyLogger, redisProxyLogger, grpcEventLogger))
internal/logging/README.md (1)

130-136: Fix ErrorLogger example to use the instance-based API.

Line 134 calls logging.ErrorLogger(...), but the new API exposes ErrorLogger as a method on the default logger. This is inconsistent with all other examples in this document and won't compile.

📝 Suggested fix
 #### Error Handling
 
 ```go
 err := errors.New("connection failed")
-logger := logging.ErrorLogger("ModuleName", "ConnectionError", err)
+logger := logging.GetDefaultLogger().ErrorLogger("ModuleName", "ConnectionError", err)
 logger.Error("Error occurred")
</details>

</blockquote></details>
<details>
<summary>install/kubernetes/principal/principal-deployment.yaml (1)</summary><blockquote>

`42-59`: **Env var naming inconsistency for subsystem log levels.**

</blockquote></details>
<details>
<summary>principal/resourceproxy/options.go (1)</summary><blockquote>

`48-55`: **Make `WithLogger` a standalone option function (receiver not needed).**  
  

<details>
<summary>🔧 Proposed fix</summary>

```diff
-func (rp *ResourceProxy) WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption {
+func WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption {
 	return func(p *ResourceProxy) error {
 		p.logger = logger
 		return nil
 	}
 }
install/helm-repo/argocd-agent-agent/templates/agent-deployment.yaml (1)

52-59: Fix redis/grpc log-level env var names to match the agent code. The current names include an extra LOG_ segment, so the agent won’t pick them up.

🐛 Proposed fix
-          - name: ARGOCD_AGENT_LOG_REDIS_PROXY_LEVEL
+          - name: ARGOCD_AGENT_REDIS_PROXY_LOG_LEVEL
             valueFrom:
               configMapKeyRef:
                 name: {{ include "argocd-agent-agent.paramsConfigMapName" . }}
                 key: agent.log.redis-proxy-level
                 optional: true
-          - name: ARGOCD_AGENT_LOG_GRPC_LOG_LEVEL
+          - name: ARGOCD_AGENT_GRPC_LOG_LEVEL
             valueFrom:
               configMapKeyRef:
                 name: {{ include "argocd-agent-agent.paramsConfigMapName" . }}
                 key: agent.log.grpc-level
                 optional: true
install/helm-repo/argocd-agent-agent/templates/tests/test-configMap.yaml (1)

78-103: Fix typos in the new ConfigMap tests (they will fail as written). The file name and error message contain copy‑paste mistakes.

🐛 Proposed fix
-        # Test agent.log.level
+        # Test agent.log.resource-proxy-level
         if [ -f /etc/config/agent.log.resource-proxy-level ]; then
           echo "✓ agent.log.resource-proxy-level exists"
-          cat /etc/config/agent.log.resource-proxy-levellevel
+          cat /etc/config/agent.log.resource-proxy-level
         else
-          echo "✗ agent.log.resrouce-proxy-levellevel missing"
+          echo "✗ agent.log.resource-proxy-level missing"
           exit 1
         fi
         
-        # Test agent.log.level
+        # Test agent.log.redis-proxy-level
         if [ -f /etc/config/agent.log.redis-proxy-level ]; then
           echo "✓ agent.log.redis-proxy-level exists"
           cat /etc/config/agent.log.redis-proxy-level
         else
           echo "✗ agent.log.redis-proxy-level missing"
           exit 1
         fi
         
-        # Test agent.log.level
+        # Test agent.log.grpc-level
         if [ -f /etc/config/agent.log.grpc-level ]; then
           echo "✓ agent.log.grpc-level exists"
           cat /etc/config/agent.log.grpc-level
         else
           echo "✗ agent.log.grpc-level missing"
           exit 1
         fi
install/kubernetes/agent/agent-deployment.yaml (1)

42-59: Environment variable naming inconsistency.

The naming patterns are inconsistent across the three new environment variables:

  • ARGOCD_AGENT_RESOURCE_PROXY_LOG_LEVEL — uses *_<COMPONENT>_LOG_LEVEL
  • ARGOCD_AGENT_LOG_REDIS_PROXY_LEVEL — uses *_LOG_<COMPONENT>_LEVEL
  • ARGOCD_AGENT_LOG_GRPC_LOG_LEVEL — has redundant LOG (LOG_GRPC_LOG_LEVEL)

Consider adopting a consistent pattern such as ARGOCD_AGENT_<COMPONENT>_LOG_LEVEL:

  • ARGOCD_AGENT_RESOURCE_PROXY_LOG_LEVEL
  • ARGOCD_AGENT_REDIS_PROXY_LOG_LEVEL
  • ARGOCD_AGENT_GRPC_EVENT_LOG_LEVEL
docs/configuration/agent/configuration.md (1)

204-222: Inconsistent heading levels for new subsystem log options.

Lines 204 and 214 use ### heading level while the preceding "Resource Proxy Log Level" (line 194) and all other parameters use ####. This creates inconsistent document hierarchy.

📝 Suggested fix
-### Redis Proxy Log Level
+#### Redis Proxy Log Level
 ...

-### GRPC Event Log Level
+#### GRPC Event Log Level
🧹 Nitpick comments (3)
internal/logging/logging_test.go (1)

416-416: Fix typo in test name: TestSeperateLoggerLevelsTestSeparateLoggerLevels.

Minor spelling correction for consistency.

✏️ Proposed fix
-func TestSeperateLoggerLevels(t *testing.T) {
+func TestSeparateLoggerLevels(t *testing.T) {
principal/apis/eventstream/eventstream.go (1)

496-499: Remove commented-out dead code.

This commented-out helper function and the uncertainty comment should be removed. The logging refactor is complete—call sites now use the injected logger instance directly.

🧹 Proposed cleanup
-// Not sure what to do with below!
-// func log() *logrus.Entry {
-// 	return logging.GetDefaultLogger().ModuleLogger("grpc.AppStream")
-// }
principal/resourceproxy/resourceproxy.go (1)

236-238: Consider removing unused log() helper or updating its usage.

The log() helper is now only used at line 121 for HTTP error logging. If line 121 is updated to use p.logger, this helper can be removed entirely to complete the centralized logging migration.

@cjcocokrisp cjcocokrisp force-pushed the feat/toggle-logging-for-subsystems branch 4 times, most recently from 80d6a8a to fd5068d Compare January 19, 2026 21:17
@jannfis
Copy link
Collaborator

jannfis commented Jan 19, 2026

Great stuff, @cjcocokrisp. Let me think a while about your thoughts of having package-scoped constants. I think we used a package-private function to initialize/get the logger for a while, not sure if we still do. But that might work too, i.e. it was/is

func logger() *logrus.Entry {
 return ... // The logger
}

However, I have one thought already: I think having dedicated command line flags (and all that which comes with it, such as new environment variables, changes to K8s resources, etc) will not scale. I would prefer keeping a single --log-level switch, have it take multiple arguments (comma separated) and encode the log levels in its argument, i.e. [<component>=]<level>. For example,

# set all log levels to debug
--log-level debug 
# set all log levels to trace, but redis proxy to debug
--log-level trace,redis-proxy=debug
# Keep log levels at their default, just set redis proxy to trace
--log-level redis-proxy=trace

So all we need to introduce for new components is a new logger, a new keyword to parse and surrounding logic to initialize that specific logger, instead of having to introduce a new command line switch etc.

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

🤖 Fix all issues with AI agents
In `@cmd/argocd-agent/agent.go`:
- Around line 142-147: CreateLogger currently hard-codes "resource proxy" in its
invalid-level error which is misleading when used for redis/grpc loggers; change
cmdutil.CreateLogger to either accept a subsystem name parameter (e.g.,
CreateLogger(level, format, subsystemName)) or make its error message generic
("invalid log level: %q") and include a passed-in subsystemName in the emitted
log/returned error. Update all call sites (resourceProxyLogger,
redisProxyLogger, grpcEventLogger where cmdutil.CreateLogger is invoked) to pass
the correct subsystem identifier or rely on the generic message, and ensure
agent.WithSubsystemLoggers continues to receive the three logger objects
unchanged.

@cjcocokrisp
Copy link
Collaborator Author

@jannfis Sounds good thanks for the prompt review and thoughts so far. I do agree that the comma separated values is a better way to scale. Will go and make those changes tomorrow.

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

🤖 Fix all issues with AI agents
In `@cmd/cmdutil/log_test.go`:
- Around line 113-124: Tests mutate global logrus state via logrus.SetOutput and
logrus.SetLevel; save the original global state at the start of each subtest
(e.g., origLevel := logrus.GetLevel() and origOut :=
logrus.StandardLogger().Out) and restore it with a defer
(logrus.SetLevel(origLevel); logrus.SetOutput(origOut)) so ParseLogLevels and
test-level output changes do not leak between tests; update the loop surrounding
ParseLogLevels/SubSystemLoggers accordingly.
♻️ Duplicate comments (1)
cmd/cmdutil/log.go (1)

102-126: Avoid duplicate log lines by discarding the default logger output.
With hooks installed, leaving the logger’s default output enabled can emit duplicates.

💡 Proposed fix
 func CreateLogger(logFormat string) *logrus.Logger {
 	logger := logrus.New()
+	logger.SetOutput(io.Discard) // route output solely through hooks
 	if formatter, err := LogFormatter(logFormat); err != nil {
 		Fatal("%s", err.Error())
 	} else {
 		logger.SetFormatter(formatter)
 	}

@cjcocokrisp
Copy link
Collaborator Author

cjcocokrisp commented Jan 20, 2026

I altered the log level CLI flag to now use the comma separated list and added a parsing helper function for that along with some tests for it let me know what you think about doing that. I made it so the general level (not specifying a subsystem) can be placed anywhere in the list just for convince. So to add a logger you would just need to update the parser function and add an entry to the struct then link to the component its used for. I also reverted the k8s manifests.

I did think about one thing for the constants for the logs. As I mentioned the big issue is needing to replace the calls to the log() function. For some areas just changing that function to now use the subsystem logger works but the big one that got me questioning was for the agent specifically. It uses the "Agent" module everywhere so that is a case where just replacing the log() function won't work. A thought I had was what if you just created other functions like that for example logGrpcEvents(), logRedisProxy(), etc. However then the problem becomes as I have it right now the loggers are fields in the struct for the components. With that we'd still probably need a global constant per package for the logger.

Still need to fix a regression in the unit tests I'm probably just not setting a default log somewhere.

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

🤖 Fix all issues with AI agents
In `@docs/configuration/principal/configuration.md`:
- Around line 341-347: The example in the log level documentation uses an
invalid level "warning"; update the example string to use a documented level
(e.g., replace "info,grpc-event=warning" with "info,grpc-event=warn") so it
matches the valid values list; ensure the Format/Example section referencing the
log level and the example string is updated accordingly.

@cjcocokrisp cjcocokrisp force-pushed the feat/toggle-logging-for-subsystems branch from bfa345e to bc179be Compare January 21, 2026 14:42
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

🤖 Fix all issues with AI agents
In `@agent/agent.go`:
- Around line 175-176: Remove the leftover debugging stdout prints: delete the
two fmt.Println(...) calls that print a.redisProxyLogger and
logging.GetDefaultLogger() in agent.go; if the intent was to inspect those
values, replace them with structured logging via the existing logger (e.g., use
a.redisProxyLogger.Debugf/Infof or logging.GetDefaultLogger().Debug/Info) so
output goes through the app's logging system instead of raw fmt.Println.

@cjcocokrisp cjcocokrisp force-pushed the feat/toggle-logging-for-subsystems branch from 4775b80 to ff5de48 Compare January 21, 2026 15:12
@jannfis
Copy link
Collaborator

jannfis commented Jan 21, 2026

@cjcocokrisp #712 has changed the documentation structure and layout a bit, I think that's where the conflict is coming from.

@cjcocokrisp cjcocokrisp force-pushed the feat/toggle-logging-for-subsystems branch from ff5de48 to e1e81dd Compare January 21, 2026 18:42
@cjcocokrisp
Copy link
Collaborator Author

Just fixed the conflicts.

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: 6

🤖 Fix all issues with AI agents
In `@cmd/argocd-agent/agent.go`:
- Around line 257-260: The --log-level flag help text is unclear about accepted
syntax; update the StringSliceVar call that registers the flag
(command.Flags().StringSliceVar with variable logLevels and flag name
"log-level") to describe that values are comma-separated and may be qualified by
subsystem (e.g. subsystem:level or subsystem1,subsystem2:level), and provide the
default of "info" in the message so users know both format and default.

In `@cmd/argocd-agent/principal.go`:
- Around line 344-346: Update the flag help for the "log-level" flag (where
command.Flags().StringSliceVar(&logLevels, "log-level", ... ) is defined) to
explicitly state that values are comma-separated and that
subsystem/component-qualified entries are allowed in the form "subsystem=level"
(e.g. "grpc=debug,store=warn") and note the default "info"; change the
description string accordingly to reflect this syntax.

In `@docs/configuration/reference/principal.md`:
- Around line 402-406: The docs list of log levels in principal.md is missing
"fatal" while the parser (StringToLoglevel in cmd/cmdutil/log.go) accepts it;
update the documentation to include `fatal` in the **Valid Values (level)** list
(trace, debug, info, warning, error, fatal) so the docs match the parser, or
alternatively remove/deny `fatal` from StringToLoglevel—ensure the change is
applied consistently and mention StringToLoglevel in the commit message.

In `@internal/logging/logging_test.go`:
- Around line 395-399: The test calls defaultLogger.SetupLogging and
newLogger.SetupLogging without checking their returned errors, so failures are
silent; update the test to capture the returned error from
defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf) and from
newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf) and immediately
fail the test if non-nil (e.g., using t.Fatalf or require.NoError) so any setup
failure is surfaced before further assertions.

In `@principal/listen.go`:
- Around line 116-119: The log is attaching the wrong error variable; in the
retry block after c, lerr = net.Listen("tcp", bind) replace the outer-scope err
used in the logger with the actual listen error lerr by changing the WithError
call to use lerr (locate the ModuleLogger("grpc").WithError(err).Debugf(...)
call in the retry loop inside listen.go and swap err -> lerr).

In `@principal/resourceproxy/options.go`:
- Around line 48-55: The WithLogger option currently assigns whatever is passed
into p.logger which can set it to nil and later cause panics; update the
WithLogger(ResourceProxyOption) closure to guard against nil by checking the
incoming logger and assigning logging.GetDefaultLogger() (or the package's
default logger accessor) when logger == nil, then set p.logger accordingly and
return nil—this preserves the ResourceProxy default logger behavior and prevents
nil derefs in methods on ResourceProxy.
♻️ Duplicate comments (3)
internal/logging/logging.go (1)

233-238: Guard nil error in ErrorLogger to avoid panic.
err.Error() will panic if err is nil.

🛠️ Suggested fix
func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
-	return l.logger.WithFields(logrus.Fields{
-		logfields.Module:    module,
-		logfields.ErrorType: errorType,
-		logfields.Error:     err.Error(),
-	})
+	fields := logrus.Fields{
+		logfields.Module:    module,
+		logfields.ErrorType: errorType,
+	}
+	if err != nil {
+		fields[logfields.Error] = err.Error()
+	}
+	return l.logger.WithFields(fields)
}
internal/logging/README.md (1)

131-135: Fix ErrorLogger example to use instance-based API.

The package-level logging.ErrorLogger no longer exists.

📝 Suggested doc fix
-logger := logging.ErrorLogger("ModuleName", "ConnectionError", err)
+logger := logging.GetDefaultLogger().ErrorLogger("ModuleName", "ConnectionError", err)
cmd/cmdutil/log.go (1)

102-128: Avoid duplicate log output by discarding the base logger output.

Line 103 creates a new logger and adds hooks, but without SetOutput(io.Discard) the default output will still emit, producing duplicate entries.

🛠️ Proposed fix
 func CreateLogger(logFormat string) *logrus.Logger {
 	logger := logrus.New()
+	logger.SetOutput(io.Discard) // prevent duplicate output (hooks handle routing)
 	if formatter, err := LogFormatter(logFormat); err != nil {
 		Fatal("%s", err.Error())
 	} else {
 		logger.SetFormatter(formatter)
 	}
🧹 Nitpick comments (3)
internal/logging/logging_test.go (1)

416-416: Minor naming nit: “Separate” spelling.

✍️ Suggested tweak
-func TestSeperateLoggerLevels(t *testing.T) {
+func TestSeparateLoggerLevels(t *testing.T) {
agent/inbound.go (1)

637-638: Preserve existing log context when unmanaging resources.
Using the prebuilt logCtx keeps method/resource fields attached to the warning.

♻️ Suggested adjustment
-		a.grpcEventLogger.ModuleLogger("Agent").Warnf("Could not unmanage app %s: %v", app.QualifiedName(), err)
+		logCtx.WithError(err).Warnf("Could not unmanage app %s", app.QualifiedName())

-		a.grpcEventLogger.ModuleLogger("Agent").Warnf("Could not unmanage appProject %s: %v", project.Name, err)
+		logCtx.WithError(err).Warnf("Could not unmanage appProject %s", project.Name)

-		a.grpcEventLogger.ModuleLogger("Agent").Warnf("Could not unmanage repository %s: %v", repo.Name, err)
+		logCtx.WithError(err).Warnf("Could not unmanage repository %s", repo.Name)

Also applies to: 757-758, 874-875

principal/apis/eventstream/eventstream.go (1)

95-98: Consider routing Subscribe/recv/send logs through the injected logger too.
Those paths still use the global logrus entry, so subsystem log levels won’t apply there.

Comment on lines +257 to 260
command.Flags().StringSliceVar(&logLevels, "log-level",
env.StringSliceWithDefault("ARGOCD_AGENT_LOG_LEVEL", nil, []string{"info"}),
"The log level for the agent")

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 | 🟡 Minor

Clarify --log-level help text for subsystem syntax.
The description should reflect comma-separated/component-qualified values.

✏️ Suggested tweak
-	"The log level for the agent")
+	"Log levels (comma-separated; optional subsystem=level, e.g. \"info,grpc-event=trace\")")
📝 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
command.Flags().StringSliceVar(&logLevels, "log-level",
env.StringSliceWithDefault("ARGOCD_AGENT_LOG_LEVEL", nil, []string{"info"}),
"The log level for the agent")
command.Flags().StringSliceVar(&logLevels, "log-level",
env.StringSliceWithDefault("ARGOCD_AGENT_LOG_LEVEL", nil, []string{"info"}),
"Log levels (comma-separated; optional subsystem=level, e.g. \"info,grpc-event=trace\")")
🤖 Prompt for AI Agents
In `@cmd/argocd-agent/agent.go` around lines 257 - 260, The --log-level flag help
text is unclear about accepted syntax; update the StringSliceVar call that
registers the flag (command.Flags().StringSliceVar with variable logLevels and
flag name "log-level") to describe that values are comma-separated and may be
qualified by subsystem (e.g. subsystem:level or subsystem1,subsystem2:level),
and provide the default of "info" in the message so users know both format and
default.

Comment on lines +344 to 346
command.Flags().StringSliceVar(&logLevels, "log-level",
env.StringSliceWithDefault("ARGOCD_PRINCIPAL_LOG_LEVEL", nil, []string{"info"}),
"The log level to use")
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 | 🟡 Minor

Clarify --log-level help text for subsystem syntax.
Current description doesn’t indicate comma-separated or component-qualified values.

✏️ Suggested tweak
-	"The log level to use")
+	"Log levels (comma-separated; optional subsystem=level, e.g. \"info,redis-proxy=debug\")")
🤖 Prompt for AI Agents
In `@cmd/argocd-agent/principal.go` around lines 344 - 346, Update the flag help
for the "log-level" flag (where command.Flags().StringSliceVar(&logLevels,
"log-level", ... ) is defined) to explicitly state that values are
comma-separated and that subsystem/component-qualified entries are allowed in
the form "subsystem=level" (e.g. "grpc=debug,store=warn") and note the default
"info"; change the description string accordingly to reflect this syntax.

Comment on lines +402 to +406
| **Type** | String (comma-separated list) |
| **Default** | `info` |
| **Valid Values** | `trace`, `debug`, `info`, `warn`, `error` |
| **Format** | `[<component>=]<level>` |
| **Valid Values (component)** | `resource-proxy`, `redis-proxy`, `grpc-event` |
| **Valid Values (level)** | `trace`, `debug`, `info`, `warning`, `error` |
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 | 🟡 Minor

Align documented log levels with actual parser.

Line 406 lists levels but omits fatal, which is accepted by StringToLoglevel in cmd/cmdutil/log.go. Either document fatal here or reject it in parsing for consistency.

📝 Suggested doc tweak
-| **Valid Values (level)** | `trace`, `debug`, `info`, `warning`, `error` |
+| **Valid Values (level)** | `trace`, `debug`, `info`, `warning`, `error`, `fatal` |
🤖 Prompt for AI Agents
In `@docs/configuration/reference/principal.md` around lines 402 - 406, The docs
list of log levels in principal.md is missing "fatal" while the parser
(StringToLoglevel in cmd/cmdutil/log.go) accepts it; update the documentation to
include `fatal` in the **Valid Values (level)** list (trace, debug, info,
warning, error, fatal) so the docs match the parser, or alternatively
remove/deny `fatal` from StringToLoglevel—ensure the change is applied
consistently and mention StringToLoglevel in the commit message.

Comment on lines +395 to +399
defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf)

newLogger := New(nil)
newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf)

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 | 🟡 Minor

Add error checks for SetupLogging in this test.

Right now failures would be silent and could produce misleading assertions.

🧪 Proposed fix
-	defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf)
+	require.NoError(t, defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf))
 
 	newLogger := New(nil)
-	newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf)
+	require.NoError(t, newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf))
📝 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
defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf)
newLogger := New(nil)
newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf)
require.NoError(t, defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf))
newLogger := New(nil)
require.NoError(t, newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf))
🤖 Prompt for AI Agents
In `@internal/logging/logging_test.go` around lines 395 - 399, The test calls
defaultLogger.SetupLogging and newLogger.SetupLogging without checking their
returned errors, so failures are silent; update the test to capture the returned
error from defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf)
and from newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf) and
immediately fail the test if non-nil (e.g., using t.Fatalf or require.NoError)
so any setup failure is surfaced before further assertions.

Comment on lines 116 to 119
c, lerr = net.Listen("tcp", bind)
if lerr != nil {
log().WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
s.options.grpcEventLogger.ModuleLogger("grpc").WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
try += 1
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 | 🟡 Minor

Log the actual listen error.

Line 118 logs err from the outer scope, which is often nil; it should log lerr from the failed net.Listen call.

🐛 Proposed fix
-			s.options.grpcEventLogger.ModuleLogger("grpc").WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
+			s.options.grpcEventLogger.ModuleLogger("grpc").WithError(lerr).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
📝 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
c, lerr = net.Listen("tcp", bind)
if lerr != nil {
log().WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
s.options.grpcEventLogger.ModuleLogger("grpc").WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
try += 1
c, lerr = net.Listen("tcp", bind)
if lerr != nil {
s.options.grpcEventLogger.ModuleLogger("grpc").WithError(lerr).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
try += 1
🤖 Prompt for AI Agents
In `@principal/listen.go` around lines 116 - 119, The log is attaching the wrong
error variable; in the retry block after c, lerr = net.Listen("tcp", bind)
replace the outer-scope err used in the logger with the actual listen error lerr
by changing the WithError call to use lerr (locate the
ModuleLogger("grpc").WithError(err).Debugf(...) call in the retry loop inside
listen.go and swap err -> lerr).

Comment on lines +48 to +55
// WithLogger sets the logger for the proxy to what is passed in. If this option is not provided
// the New function will set it to the default logger
func WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption {
return func(p *ResourceProxy) error {
p.logger = logger
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 | 🟡 Minor

Guard against nil to preserve default logger behavior.
If a caller passes nil, rp.logger can become nil and later logging will panic. Consider defaulting to GetDefaultLogger() when logger is nil.

🛠️ Suggested fix
func WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption {
+	if logger == nil {
+		logger = logging.GetDefaultLogger()
+	}
 	return func(p *ResourceProxy) error {
 		p.logger = logger
 		return nil
 	}
}
📝 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
// WithLogger sets the logger for the proxy to what is passed in. If this option is not provided
// the New function will set it to the default logger
func WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption {
return func(p *ResourceProxy) error {
p.logger = logger
return nil
}
}
// WithLogger sets the logger for the proxy to what is passed in. If this option is not provided
// the New function will set it to the default logger
func WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption {
if logger == nil {
logger = logging.GetDefaultLogger()
}
return func(p *ResourceProxy) error {
p.logger = logger
return nil
}
}
🤖 Prompt for AI Agents
In `@principal/resourceproxy/options.go` around lines 48 - 55, The WithLogger
option currently assigns whatever is passed into p.logger which can set it to
nil and later cause panics; update the WithLogger(ResourceProxyOption) closure
to guard against nil by checking the incoming logger and assigning
logging.GetDefaultLogger() (or the package's default logger accessor) when
logger == nil, then set p.logger accordingly and return nil—this preserves the
ResourceProxy default logger behavior and prevents nil derefs in methods on
ResourceProxy.

@codecov-commenter
Copy link

codecov-commenter commented Jan 21, 2026

Codecov Report

❌ Patch coverage is 54.12844% with 150 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.14%. Comparing base (e47703c) to head (911fb16).

Files with missing lines Patch % Lines
cmd/cmdutil/log.go 48.38% 29 Missing and 3 partials ⚠️
internal/logging/logging.go 66.19% 24 Missing ⚠️
internal/logging/examples/migration_examples.go 0.00% 16 Missing ⚠️
principal/options.go 0.00% 15 Missing ⚠️
cmd/argocd-agent/principal.go 0.00% 13 Missing ⚠️
cmd/argocd-agent/agent.go 0.00% 12 Missing ⚠️
principal/redisproxy/redisproxy.go 16.66% 10 Missing ⚠️
agent/options.go 0.00% 6 Missing ⚠️
agent/inbound.go 72.22% 5 Missing ⚠️
principal/apis/eventstream/eventstream.go 50.00% 4 Missing ⚠️
... and 8 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #711      +/-   ##
==========================================
+ Coverage   46.06%   46.14%   +0.07%     
==========================================
  Files          99       99              
  Lines       12273    12413     +140     
==========================================
+ Hits         5654     5728      +74     
- Misses       6068     6130      +62     
- Partials      551      555       +4     

☔ View full report in Codecov by Sentry.
📢 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.

…rus logger

Signed-off-by: Christopher Coco <ccoco@redhat.com>
… changes

Signed-off-by: Christopher Coco <ccoco@redhat.com>
…eady created

Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
…x typo i made on docs and in the principal cmd

Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
Signed-off-by: Christopher Coco <ccoco@redhat.com>
@cjcocokrisp cjcocokrisp force-pushed the feat/toggle-logging-for-subsystems branch from e1e81dd to 906da23 Compare January 22, 2026 13:06
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
principal/redisproxy/redisproxy.go (2)

88-112: Inconsistent logger usage in Start().

The Start() method uses the package-level log() helper (lines 92, 102, 109) instead of the instance's rp.logger. This defeats the purpose of having a configurable per-instance logger.

Proposed fix
 func (rp *RedisProxy) Start() error {
 	l, err := net.Listen("tcp", rp.listenAddress)
 	if err != nil {
-		log().WithError(err).Error("error occurred on listening to addr: " + rp.listenAddress)
+		rp.logger.ModuleLogger("redisProxy").WithError(err).Error("error occurred on listening to addr: " + rp.listenAddress)
 		return err
 	}
 	rp.listener = l

 	// Start server and connection handler
 	go func() {
 		for {
 			conn, err := l.Accept()
 			if err != nil {
-				log().WithError(err).Error("error occurred on accepting connection")
+				rp.logger.ModuleLogger("redisProxy").WithError(err).Error("error occurred on accepting connection")
 				return
 			}
 			go rp.handleConnection(conn)
 		}
 	}()

-	log().Infof("Redis proxy started on %s", rp.listenAddress)
+	rp.logger.ModuleLogger("redisProxy").Infof("Redis proxy started on %s", rp.listenAddress)

 	return nil
 }

708-725: Use logCtx instead of log() in error path.

Line 715 uses the package-level log() helper while logCtx is available as a parameter. This loses the connection context and bypasses the instance logger.

Proposed fix
 func (are *argoCDRedisWriterInternal) writeToArgoCDRedisSocket(logCtx *logrus.Entry, bytes []byte) error {
 	are.argoCDWriteLock.Lock()
 	defer are.argoCDWriteLock.Unlock()

 	_, err := are.fromArgoCDWrite.Write(bytes)
 	if err != nil {
-		log().WithError(err).Error("unable to write to Argo CD Redis socket")
+		logCtx.WithError(err).Error("unable to write to Argo CD Redis socket")
 		return err
 	}
🤖 Fix all issues with AI agents
In `@internal/logging/logging_test.go`:
- Line 416: Rename the test function TestSeperateLoggerLevels to
TestSeparateLoggerLevels (fixing the "Seperate" → "Separate" typo) and update
any references or calls to that symbol (e.g., in test suites or other files) so
the new name is used consistently; run `go test` to ensure the renamed test is
discovered and passes.
♻️ Duplicate comments (10)
principal/resourceproxy/options.go (1)

48-55: Guard against nil to preserve default logger behavior.

If a caller passes nil, rp.logger will be set to nil. Although New() has a fallback that assigns GetDefaultLogger() when p.logger == nil, calling WithLogger(nil) on an existing ResourceProxy instance (via WithRequestMatcher pattern or direct mutation) could still result in a nil logger.

🛠️ Suggested fix
 func WithLogger(logger *logging.CentralizedLogger) ResourceProxyOption {
+	if logger == nil {
+		logger = logging.GetDefaultLogger()
+	}
 	return func(p *ResourceProxy) error {
 		p.logger = logger
 		return nil
 	}
 }
internal/logging/logging_test.go (1)

391-414: Add error checks for SetupLogging calls.

The SetupLogging calls at lines 395 and 398 ignore the returned error, which could cause silent failures and misleading test assertions.

🧪 Proposed fix
-	defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf)
+	require.NoError(t, defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf))
 
 	newLogger := New(nil)
-	newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf)
+	require.NoError(t, newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf))
docs/configuration/reference/principal.md (1)

402-408: Documentation updates look good overall.

The new format documentation correctly describes the comma-separated log level syntax with optional component qualifiers. The component list (resource-proxy, redis-proxy, grpc-event) aligns with the AvailableSubSystems constant in cmd/cmdutil/log.go.

However, the valid log levels list is still missing fatal, which StringToLoglevel accepts. This was flagged in a previous review.

cmd/cmdutil/log.go (1)

102-129: Missing SetOutput(io.Discard) will cause duplicate log output.

The CreateLogger function adds writer hooks but doesn't discard the default output. Since logrus writes to both the default output and hooks, logs will be duplicated (once to stderr/stdout via the default writer, and again via the hooks).

Proposed fix
 func CreateLogger(logFormat string) *logrus.Logger {
 	logger := logrus.New()
+	logger.SetOutput(io.Discard) // Route all logs through hooks only
 	if formatter, err := LogFormatter(logFormat); err != nil {
 		Fatal("%s", err.Error())
 	} else {
principal/redisproxy/redisproxy.go (1)

245-245: Fix typo in comment.

The comment contains a corrupted word: falsGetDefaultLogger().e should be false. This was flagged in a previous review.

Proposed fix
-	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: falsGetDefaultLogger().e if the command has already been processed, true otherwise.
+	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: false if the command has already been processed, true otherwise.
principal/listen.go (1)

116-121: Logging the wrong error variable.

Line 118 logs err from the outer scope (which is often nil at this point) instead of lerr from the failed net.Listen call. This was flagged in a previous review.

Proposed fix
 		c, lerr = net.Listen("tcp", bind)
 		if lerr != nil {
-			s.options.grpcEventLogger.ModuleLogger("grpc").WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
+			s.options.grpcEventLogger.ModuleLogger("grpc").WithError(lerr).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
 			try += 1
 			return false, lerr
 		}
internal/logging/README.md (1)

130-136: ErrorLogger example still uses the old package-level API.

Line 134 calls logging.ErrorLogger(...) directly instead of logging.GetDefaultLogger().ErrorLogger(...). This example won't compile with the new instance-based API.

Proposed fix
 #### Error Handling

 ```go
 err := errors.New("connection failed")
-logger := logging.ErrorLogger("ModuleName", "ConnectionError", err)
+logger := logging.GetDefaultLogger().ErrorLogger("ModuleName", "ConnectionError", err)
 logger.Error("Error occurred")
</details>

</blockquote></details>
<details>
<summary>internal/logging/logging.go (1)</summary><blockquote>

`233-238`: **Guard against nil error to avoid panic in ErrorLogger.**

`err.Error()` will panic when `err` is nil. Logging should be safe even with a nil error.  
 
<details>
<summary>🐛 Proposed fix</summary>

```diff
 func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
-	return l.logger.WithFields(logrus.Fields{
-		logfields.Module:    module,
-		logfields.ErrorType: errorType,
-		logfields.Error:     err.Error(),
-	})
+	fields := logrus.Fields{
+		logfields.Module:    module,
+		logfields.ErrorType: errorType,
+	}
+	if err != nil {
+		fields[logfields.Error] = err.Error()
+	}
+	return l.logger.WithFields(fields)
 }
cmd/argocd-agent/agent.go (1)

257-259: Clarify --log-level help text for new syntax.
The flag now supports comma-separated and subsystem-qualified values, but the description still reads like a single global level.

✏️ Suggested tweak
-	"The log level for the agent")
+	"Log levels (comma-separated; optional subsystem=level, e.g. \"info,redis-proxy=debug\")")
cmd/argocd-agent/principal.go (1)

344-346: Clarify --log-level help text for new syntax.
The flag now accepts comma-separated and subsystem-qualified values, but the description still reads like a single global level.

✏️ Suggested tweak
-	"The log level to use")
+	"Log levels (comma-separated; optional subsystem=level, e.g. \"info,redis-proxy=debug\")")
🧹 Nitpick comments (4)
principal/resourceproxy/resourceproxy.go (1)

121-121: Inconsistent logger usage between log() helper and instance logger.

The log() helper (used at line 121 for HTTP error logging) returns GetDefaultLogger().ModuleLogger("proxy"), while the rest of the file uses rp.logger.ModuleLogger("proxy"). This means HTTP server error logs will always use the default logger's level, ignoring any custom logger injected via WithLogger.

Consider using the instance logger consistently:

♻️ Suggested refactor
 	httpLogger := log().WriterLevel(logrus.ErrorLevel)
+	// Note: Using log() here means HTTP server errors use the default logger,
+	// not the instance's configured logger. Consider:
+	// httpLogger := rp.logger.ModuleLogger("proxy").WriterLevel(logrus.ErrorLevel)

Alternatively, if retaining log() for backward compatibility:

-func log() *logrus.Entry {
-	return logging.GetDefaultLogger().ModuleLogger("proxy")
-}
+// log is retained for HTTP server error logging using the default logger.
+// Instance-specific logging should use rp.logger.ModuleLogger("proxy").
+func log() *logrus.Entry {
+	return logging.GetDefaultLogger().ModuleLogger("proxy")
+}

Also applies to: 236-238

cmd/cmdutil/log.go (3)

133-141: Use a map for seen and remove impossible condition.

  1. seen is used for membership checks, but slices.Contains is O(n). With a map, lookups are O(1).
  2. len(split) == 0 is impossible—strings.Split always returns at least one element (an empty string if e is empty).
Proposed fix
 func ParseLogLevels(input []string, ss *SubSystemLoggers) {
-	seen := []string{}
+	seen := make(map[string]bool)

 	for _, e := range input {
 		split := strings.Split(e, "=")
-		if len(split) > 2 || len(split) == 0 {
+		if len(split) > 2 {
 			logrus.Warnf("%s is invalid please use the format subsystem=loglevel, skipping", e)
 			continue
 		}

154-163: Update seen checks to use map syntax.

Following the refactor to use a map for seen, update the membership checks and assignments accordingly.

Proposed fix
-		if !slices.Contains(seen, "resource-proxy") {
+		if !seen["resource-proxy"] {
 			ss.ResourceProxyLogger.SetLevel(level)
 		}
-		if !slices.Contains(seen, "redis-proxy") {
+		if !seen["redis-proxy"] {
 			ss.RedisProxyLogger.SetLevel(level)
 		}
-		if !slices.Contains(seen, "grpc-event") {
+		if !seen["grpc-event"] {
 			ss.GrpcEventLogger.SetLevel(level)
 		}

And for the switch cases (lines 175-183):

 	case "resource-proxy":
 		ss.ResourceProxyLogger.SetLevel(level)
-		seen = append(seen, "resource-proxy")
+		seen["resource-proxy"] = true
 	case "redis-proxy":
 		ss.RedisProxyLogger.SetLevel(level)
-		seen = append(seen, "redis-proxy")
+		seen["redis-proxy"] = true
 	case "grpc-event":
 		ss.GrpcEventLogger.SetLevel(level)
-		seen = append(seen, "grpc-event")
+		seen["grpc-event"] = true

21-21: Remove unused import after refactor.

If the seen slice is converted to a map, the slices import will become unused and should be removed.

@cjcocokrisp cjcocokrisp force-pushed the feat/toggle-logging-for-subsystems branch from 8def3fd to 7450284 Compare January 22, 2026 16:06
@cjcocokrisp
Copy link
Collaborator Author

Changed how the subsystem loggers are interacted with to use the log() functions in each package and in cases where multiple are needed created other log functions to use. This fixed the unused function linting problem so all CI checks should hopefully pass now.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
principal/logging.go (1)

62-68: Fix log message to match stream interceptor.

The stream interceptor still logs “unary,” which is misleading.

💡 Proposed fix
-		}).Debug("Processing unary gRPC request")
+		}).Debug("Processing stream gRPC request")
🤖 Fix all issues with AI agents
In `@principal/redisproxy/redisproxy.go`:
- Around line 129-131: The code currently calls rp.logger.ModuleLogger(...)
which can panic when rp.logger is nil; replace direct rp.logger access with
rp.log() so the call becomes
rp.log().ModuleLogger("redisProxy").WithField("function", "redisFxn") and then
.WithField("connUUID", connUUID) (update the variable logCtx assignment). Search
for other direct rp.logger.ModuleLogger usages in the redisFxn scope and swap
them to rp.log() to ensure safe fallback to the default logger.
♻️ Duplicate comments (4)
internal/logging/logging.go (1)

233-238: Guard against nil error to avoid panic.

err.Error() will panic if err is nil. Logging should be safe when callers pass nil.

🐛 Proposed fix
 func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
-	return l.logger.WithFields(logrus.Fields{
-		logfields.Module:    module,
-		logfields.ErrorType: errorType,
-		logfields.Error:     err.Error(),
-	})
+	fields := logrus.Fields{
+		logfields.Module:    module,
+		logfields.ErrorType: errorType,
+	}
+	if err != nil {
+		fields[logfields.Error] = err.Error()
+	}
+	return l.logger.WithFields(fields)
 }
principal/redisproxy/redisproxy.go (1)

245-245: Fix typo in comment.

The inline comment has a corrupted word.

📝 Proposed fix
-	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: falsGetDefaultLogger().e if the command has already been processed, true otherwise.
+	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: false if the command has already been processed, true otherwise.
principal/listen.go (1)

116-119: Use the actual listen error in retry log.

The retry log uses err from the outer scope, which is often nil; it should log lerr from net.Listen.

🐛 Proposed fix
-			s.logGrpcEvent().WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
+			s.logGrpcEvent().WithError(lerr).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
internal/logging/logging_test.go (1)

391-399: Add error checks for SetupLogging in this test.

Setup failures would be silent and could produce misleading assertions.

🧪 Proposed fix
-	defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf)
+	require.NoError(t, defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf))
 
 	newLogger := New(nil)
-	newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf)
+	require.NoError(t, newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf))
🧹 Nitpick comments (1)
principal/resourceproxy/resourceproxy_test.go (1)

47-47: Consider using the resource-proxy logger for consistency.

♻️ Suggested tweak
-				logging.GetDefaultLogger().ModuleLogger("RedisProxy").Tracef("Writing OK")
+				p.log().Tracef("Writing OK")

Signed-off-by: Christopher Coco <ccoco@redhat.com>
@cjcocokrisp cjcocokrisp force-pushed the feat/toggle-logging-for-subsystems branch from 7450284 to 911fb16 Compare January 22, 2026 16:19
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
principal/logging.go (1)

62-68: Fix log message for stream interceptor.
It currently says “unary” in the stream path.

📝 Proposed fix
-		}).Debug("Processing unary gRPC request")
+		}).Debug("Processing stream gRPC request")
♻️ Duplicate comments (4)
principal/listen.go (1)

117-120: Fix: Log the actual listen error.

The log statement uses err (outer scope, likely nil at this point) instead of lerr (the actual error from net.Listen). This was flagged in a previous review but remains unfixed.

🐛 Proposed fix
-			s.logGrpcEvent().WithError(err).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
+			s.logGrpcEvent().WithError(lerr).Debugf("Retrying to start TCP listener on %s (retry %d/%d)", bind, try, listenerRetries)
principal/redisproxy/redisproxy.go (1)

245-245: Fix typo in inline comment.
Minor but confusing doc glitch.

📝 Proposed fix
-	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: falsGetDefaultLogger().e if the command has already been processed, true otherwise.
+	forwardRawCommandToPrincipalRedis := true // whether to send the command to principal redis: false if the command has already been processed, true otherwise.
internal/logging/logging.go (1)

233-238: Guard against nil err to avoid panic.
err.Error() will panic when err is nil.

🐛 Proposed fix
 func (l *CentralizedLogger) ErrorLogger(module, errorType string, err error) *logrus.Entry {
-	return l.logger.WithFields(logrus.Fields{
-		logfields.Module:    module,
-		logfields.ErrorType: errorType,
-		logfields.Error:     err.Error(),
-	})
+	fields := logrus.Fields{
+		logfields.Module:    module,
+		logfields.ErrorType: errorType,
+	}
+	if err != nil {
+		fields[logfields.Error] = err.Error()
+	}
+	return l.logger.WithFields(fields)
 }
internal/logging/logging_test.go (1)

395-399: Add error checks for SetupLogging in this test.
Silent failures can mask setup issues.

🧪 Proposed fix
-	defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf)
+	require.NoError(t, defaultLogger.SetupLogging(LogLevelInfo, LogFormatJSON, &defaultBuf))
 
 	newLogger := New(nil)
-	newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf)
+	require.NoError(t, newLogger.SetupLogging(LogLevelDebug, LogFormatJSON, &newBuf))
🧹 Nitpick comments (1)
principal/server.go (1)

216-227: Optional: Redundant nil-to-default logger assignments.

The nil checks for resourceProxyLogger and redisProxyLogger are redundant because both resourceproxy.New() (line 110-112) and redisproxy.New() (line 71-73) already handle nil loggers by defaulting to GetDefaultLogger(). Similarly, logGrpcEvent() uses SelectLogger() which handles nil.

This defensive initialization is harmless but creates duplication. Consider removing these checks to keep the default-logger logic in one place (the constructors/accessors).

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.

Ability to toggle logging from certain sub systems

3 participants