Google Stackdriver日志Severity字段在Cloud Run与Compute Engine(Ops Agent)中的兼容性问题及解决方案咨询
Yes, this is expected behavior, and it boils down to how each environment ingests and processes logs:
- Cloud Run integrates natively with Google Cloud Logging. When you write structured logs to stdout/stderr, Cloud Run parses them as full
LogEntryobjects automatically. That’s why using the plainseverityfield works here—it maps directly to theLogEntryfield defined in the Logging API. - Compute Engine with Ops Agent operates differently: the agent collects log files, parses JSON content into the
jsonPayloadfield of aLogEntry, and requires explicit mapping for special fields like severity. Without thelogging.googleapis.com/severitykey, the agent treatsseverityas just another payload attribute instead of elevating it to the top-level log entry severity.
You don’t need to maintain two separate logging packages—here are two clean solutions to support both environments with a single implementation:
Option 1: Dynamically adjust the severity field name at serialization
You can override the JSON serialization of your Entry struct to use the correct field name based on the runtime environment. Cloud Run automatically sets the K_SERVICE environment variable, so we can use that to detect the environment.
First, modify your Entry struct to skip default serialization for the Severity field, then implement a custom MarshalJSON method:
package yourlogger import ( "encoding/json" "os" ) type Severity string const ( SeverityInfo Severity = "INFO" SeverityError Severity = "ERROR" // Add other severity levels as needed ) type Entry struct { Message string `json:"message"` Severity Severity `json:"-"` // Exclude from default JSON serialization Trace string `json:"logging.googleapis.com/trace,omitempty"` Component string `json:"component,omitempty"` } func (e Entry) MarshalJSON() ([]byte, error) { payload := make(map[string]interface{}) payload["message"] = e.Message payload["component"] = e.Component if e.Trace != "" { payload["logging.googleapis.com/trace"] = e.Trace } // Detect if we're running in Cloud Run isCloudRun := os.Getenv("K_SERVICE") != "" if isCloudRun { payload["severity"] = string(e.Severity) } else { payload["logging.googleapis.com/severity"] = string(e.Severity) } return json.Marshal(payload) }
Then update your Log function to serialize the entry properly before logging:
func Log(ctx context.Context, severity Severity, format string, a ...interface{}) { entry := Entry{ Severity: severity, Message: fmt.Sprintf(format, a...), Component: "arbitrary-property", Trace: GetTraceId(ctx), } logJSON, err := json.Marshal(entry) if err != nil { // Fallback to plain text logging if serialization fails log.Printf("Failed to serialize log entry: %v | Message: %s", err, fmt.Sprintf(format, a...)) return } log.Println(string(logJSON)) }
This way, the same logger outputs the correct severity field name whether it’s running in Cloud Run or Compute Engine.
Option 2: Configure Ops Agent to map the plain severity field
If you prefer not to modify your code, update the Ops Agent configuration on your Compute Engine instances to extract severity from the jsonPayload.severity field and elevate it to the top-level log entry severity.
Here’s an example configuration snippet (typically stored at /etc/google-cloud-ops-agent/config.yaml):
logging: receivers: your-app-receiver: type: files include_paths: - /var/log/your-app/*.log # Update to your app's log path json_field: jsonPayload severity: field: jsonPayload.severity default: INFO # Fallback severity if the field is missing processors: parse-json: type: parse_json field: message output_field: jsonPayload service: pipelines: default_pipeline: receivers: [your-app-receiver] processors: [parse-json]
After updating the config, restart the Ops Agent:
sudo systemctl restart google-cloud-ops-agent
With this setup, the Ops Agent recognizes the plain severity field in your log entries, matching how Cloud Run processes logs—so you can keep your existing logger implementation unchanged.
Both options work well—choose the one that fits your workflow:
- Option 1 is code-centric and requires no infrastructure changes.
- Option 2 keeps your code clean but requires updating Ops Agent configurations across all your Compute Engine instances.
内容的提问来源于stack exchange,提问作者Avishay28

