From 19ee39c12320f612bd112f86c32c746c4c89b920 Mon Sep 17 00:00:00 2001 From: AnnatarHe Date: Sat, 20 Dec 2025 00:21:57 +0800 Subject: [PATCH 1/3] refactor(otel): remove session concept, embed resource attrs in metrics/events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove CCOtelSession struct entirely - Add CCOtelResourceAttributes helper for extracting resource-level attributes - Embed all resource attributes directly in CCOtelMetric and CCOtelEvent: - Standard: sessionId, userAccountUuid, organizationId, terminalType, appVersion, osType, osVersion, hostArch - Additional: userId, userEmail - Custom (OTEL_RESOURCE_ATTRIBUTES): userName, machineName, teamId - Add EventTimestamp field for ISO 8601 timestamp - Add getIntFromValue/getFloatFromValue helpers to handle string-encoded numeric values - Update CCOtelRequest to flat structure: { host, project, metrics[], events[] } - Remove SessionID from CCOtelResponse Based on Claude Code monitoring documentation which specifies attributes should be on individual metrics/events, not in a separate session entity. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- daemon/ccotel_processor.go | 214 +++++++++++++++++++++++++++++-------- model/ccotel_types.go | 94 +++++++++++----- 2 files changed, 236 insertions(+), 72 deletions(-) diff --git a/daemon/ccotel_processor.go b/daemon/ccotel_processor.go index 7881742..5971a10 100644 --- a/daemon/ccotel_processor.go +++ b/daemon/ccotel_processor.go @@ -5,7 +5,7 @@ import ( "encoding/json" "log/slog" "os" - "time" + "strconv" "github.com/google/uuid" "github.com/malamtime/cli/model" @@ -54,14 +54,15 @@ func (p *CCOtelProcessor) ProcessMetrics(ctx context.Context, req *collmetricsv1 continue } - session := extractSessionFromResource(resource) + // Extract resource attributes once for all metrics in this resource + resourceAttrs := extractResourceAttributes(resource) project := p.detectProject(resource) var metrics []model.CCOtelMetric for _, sm := range rm.GetScopeMetrics() { for _, m := range sm.GetMetrics() { - parsedMetrics := p.parseMetric(m) + parsedMetrics := p.parseMetric(m, resourceAttrs) metrics = append(metrics, parsedMetrics...) } } @@ -70,11 +71,10 @@ func (p *CCOtelProcessor) ProcessMetrics(ctx context.Context, req *collmetricsv1 continue } - // Build and send request immediately + // Build and send request immediately - flat structure without session ccReq := &model.CCOtelRequest{ Host: p.hostname, Project: project, - Session: session, Metrics: metrics, } @@ -103,14 +103,15 @@ func (p *CCOtelProcessor) ProcessLogs(ctx context.Context, req *collogsv1.Export continue } - session := extractSessionFromResource(resource) + // Extract resource attributes once for all events in this resource + resourceAttrs := extractResourceAttributes(resource) project := p.detectProject(resource) var events []model.CCOtelEvent for _, sl := range rl.GetScopeLogs() { for _, lr := range sl.GetLogRecords() { - event := p.parseLogRecord(lr) + event := p.parseLogRecord(lr, resourceAttrs) if event != nil { events = append(events, *event) } @@ -121,11 +122,10 @@ func (p *CCOtelProcessor) ProcessLogs(ctx context.Context, req *collogsv1.Export continue } - // Build and send request immediately + // Build and send request immediately - flat structure without session ccReq := &model.CCOtelRequest{ Host: p.hostname, Project: project, - Session: session, Events: events, } @@ -155,14 +155,13 @@ func isClaudeCodeResource(resource *resourcev1.Resource) bool { return false } -// extractSessionFromResource extracts session info from resource attributes -func extractSessionFromResource(resource *resourcev1.Resource) *model.CCOtelSession { - session := &model.CCOtelSession{ - StartedAt: time.Now().Unix(), - } +// extractResourceAttributes extracts resource-level attributes from OTEL resource +// Returns a struct that can be used to populate metrics and events +func extractResourceAttributes(resource *resourcev1.Resource) *model.CCOtelResourceAttributes { + attrs := &model.CCOtelResourceAttributes{} if resource == nil { - return session + return attrs } for _, attr := range resource.GetAttributes() { @@ -170,35 +169,87 @@ func extractSessionFromResource(resource *resourcev1.Resource) *model.CCOtelSess value := attr.GetValue() switch key { + // Standard resource attributes case "session.id": - session.SessionID = value.GetStringValue() + attrs.SessionID = value.GetStringValue() case "app.version": - session.AppVersion = value.GetStringValue() + attrs.AppVersion = value.GetStringValue() case "organization.id": - session.OrganizationID = value.GetStringValue() + attrs.OrganizationID = value.GetStringValue() case "user.account_uuid": - session.UserAccountUUID = value.GetStringValue() + attrs.UserAccountUUID = value.GetStringValue() case "terminal.type": - session.TerminalType = value.GetStringValue() + attrs.TerminalType = value.GetStringValue() case "service.version": - session.ServiceVersion = value.GetStringValue() + attrs.ServiceVersion = value.GetStringValue() case "os.type": - session.OSType = value.GetStringValue() + attrs.OSType = value.GetStringValue() case "os.version": - session.OSVersion = value.GetStringValue() + attrs.OSVersion = value.GetStringValue() case "host.arch": - session.HostArch = value.GetStringValue() + attrs.HostArch = value.GetStringValue() case "wsl.version": - session.WSLVersion = value.GetStringValue() + attrs.WSLVersion = value.GetStringValue() + // Additional identifiers + case "user.id": + attrs.UserID = value.GetStringValue() + case "user.email": + attrs.UserEmail = value.GetStringValue() + // Custom resource attributes (from OTEL_RESOURCE_ATTRIBUTES) + case "user.name": + attrs.UserName = value.GetStringValue() + case "machine.name": + attrs.MachineName = value.GetStringValue() + case "team.id": + attrs.TeamID = value.GetStringValue() } } - // Generate session ID if not present - if session.SessionID == "" { - session.SessionID = uuid.New().String() - } + return attrs +} - return session +// applyResourceAttributesToMetric copies resource attributes into a metric +func applyResourceAttributesToMetric(metric *model.CCOtelMetric, attrs *model.CCOtelResourceAttributes) { + // Standard resource attributes + metric.SessionID = attrs.SessionID + metric.UserAccountUUID = attrs.UserAccountUUID + metric.OrganizationID = attrs.OrganizationID + metric.TerminalType = attrs.TerminalType + metric.AppVersion = attrs.AppVersion + metric.OSType = attrs.OSType + metric.OSVersion = attrs.OSVersion + metric.HostArch = attrs.HostArch + + // Additional identifiers + metric.UserID = attrs.UserID + metric.UserEmail = attrs.UserEmail + + // Custom resource attributes + metric.UserName = attrs.UserName + metric.MachineName = attrs.MachineName + metric.TeamID = attrs.TeamID +} + +// applyResourceAttributesToEvent copies resource attributes into an event +func applyResourceAttributesToEvent(event *model.CCOtelEvent, attrs *model.CCOtelResourceAttributes) { + // Standard resource attributes + event.SessionID = attrs.SessionID + event.UserAccountUUID = attrs.UserAccountUUID + event.OrganizationID = attrs.OrganizationID + event.TerminalType = attrs.TerminalType + event.AppVersion = attrs.AppVersion + event.OSType = attrs.OSType + event.OSVersion = attrs.OSVersion + event.HostArch = attrs.HostArch + + // Additional identifiers + event.UserID = attrs.UserID + event.UserEmail = attrs.UserEmail + + // Custom resource attributes + event.UserName = attrs.UserName + event.MachineName = attrs.MachineName + event.TeamID = attrs.TeamID } // detectProject extracts project from resource attributes or environment @@ -224,7 +275,7 @@ func (p *CCOtelProcessor) detectProject(resource *resourcev1.Resource) string { } // parseMetric parses an OTEL metric into CCOtelMetric(s) -func (p *CCOtelProcessor) parseMetric(m *metricsv1.Metric) []model.CCOtelMetric { +func (p *CCOtelProcessor) parseMetric(m *metricsv1.Metric, resourceAttrs *model.CCOtelResourceAttributes) []model.CCOtelMetric { var metrics []model.CCOtelMetric name := m.GetName() @@ -243,7 +294,9 @@ func (p *CCOtelProcessor) parseMetric(m *metricsv1.Metric) []model.CCOtelMetric Timestamp: int64(dp.GetTimeUnixNano() / 1e9), // Convert to seconds Value: getDataPointValue(dp), } - // Extract attributes + // Apply resource attributes first + applyResourceAttributesToMetric(&metric, resourceAttrs) + // Then extract data point attributes (can override resource attrs) for _, attr := range dp.GetAttributes() { applyMetricAttribute(&metric, attr, metricType) } @@ -257,6 +310,9 @@ func (p *CCOtelProcessor) parseMetric(m *metricsv1.Metric) []model.CCOtelMetric Timestamp: int64(dp.GetTimeUnixNano() / 1e9), Value: getDataPointValue(dp), } + // Apply resource attributes first + applyResourceAttributesToMetric(&metric, resourceAttrs) + // Then extract data point attributes (can override resource attrs) for _, attr := range dp.GetAttributes() { applyMetricAttribute(&metric, attr, metricType) } @@ -268,13 +324,16 @@ func (p *CCOtelProcessor) parseMetric(m *metricsv1.Metric) []model.CCOtelMetric } // parseLogRecord parses an OTEL log record into a CCOtelEvent -func (p *CCOtelProcessor) parseLogRecord(lr *logsv1.LogRecord) *model.CCOtelEvent { +func (p *CCOtelProcessor) parseLogRecord(lr *logsv1.LogRecord, resourceAttrs *model.CCOtelResourceAttributes) *model.CCOtelEvent { event := &model.CCOtelEvent{ EventID: uuid.New().String(), Timestamp: int64(lr.GetTimeUnixNano() / 1e9), // Convert to seconds } - // Extract event type and other attributes + // Apply resource attributes first + applyResourceAttributesToEvent(event, resourceAttrs) + + // Extract event type and other attributes from log record for _, attr := range lr.GetAttributes() { key := attr.GetKey() value := attr.GetValue() @@ -282,20 +341,22 @@ func (p *CCOtelProcessor) parseLogRecord(lr *logsv1.LogRecord) *model.CCOtelEven switch key { case "event.name": event.EventType = mapEventName(value.GetStringValue()) + case "event.timestamp": + event.EventTimestamp = value.GetStringValue() case "model": event.Model = value.GetStringValue() case "cost_usd": - event.CostUSD = value.GetDoubleValue() + event.CostUSD = getFloatFromValue(value) case "duration_ms": - event.DurationMs = int(value.GetIntValue()) + event.DurationMs = getIntFromValue(value) case "input_tokens": - event.InputTokens = int(value.GetIntValue()) + event.InputTokens = getIntFromValue(value) case "output_tokens": - event.OutputTokens = int(value.GetIntValue()) + event.OutputTokens = getIntFromValue(value) case "cache_read_tokens": - event.CacheReadTokens = int(value.GetIntValue()) + event.CacheReadTokens = getIntFromValue(value) case "cache_creation_tokens": - event.CacheCreationTokens = int(value.GetIntValue()) + event.CacheCreationTokens = getIntFromValue(value) case "tool_name": event.ToolName = value.GetStringValue() case "success": @@ -307,7 +368,7 @@ func (p *CCOtelProcessor) parseLogRecord(lr *logsv1.LogRecord) *model.CCOtelEven case "error": event.Error = value.GetStringValue() case "prompt_length": - event.PromptLength = int(value.GetIntValue()) + event.PromptLength = getIntFromValue(value) case "prompt": event.Prompt = value.GetStringValue() case "tool_parameters": @@ -321,11 +382,26 @@ func (p *CCOtelProcessor) parseLogRecord(lr *logsv1.LogRecord) *model.CCOtelEven } } case "status_code": - event.StatusCode = int(value.GetIntValue()) + event.StatusCode = getIntFromValue(value) case "attempt": - event.Attempt = int(value.GetIntValue()) + event.Attempt = getIntFromValue(value) case "language": event.Language = value.GetStringValue() + // Log record level attributes that override resource attrs + case "user.id": + event.UserID = value.GetStringValue() + case "user.email": + event.UserEmail = value.GetStringValue() + case "session.id": + event.SessionID = value.GetStringValue() + case "app.version": + event.AppVersion = value.GetStringValue() + case "organization.id": + event.OrganizationID = value.GetStringValue() + case "user.account_uuid": + event.UserAccountUUID = value.GetStringValue() + case "terminal.type": + event.TerminalType = value.GetStringValue() } } @@ -391,6 +467,36 @@ func getDataPointValue(dp *metricsv1.NumberDataPoint) float64 { } } +// getIntFromValue extracts an int from an OTEL value, handling both int and string formats +func getIntFromValue(value *commonv1.AnyValue) int { + // First try to get as int + if intVal := value.GetIntValue(); intVal != 0 { + return int(intVal) + } + // Try to parse from string (Claude Code sends some values as strings) + if strVal := value.GetStringValue(); strVal != "" { + if parsed, err := strconv.Atoi(strVal); err == nil { + return parsed + } + } + return 0 +} + +// getFloatFromValue extracts a float64 from an OTEL value, handling both double and string formats +func getFloatFromValue(value *commonv1.AnyValue) float64 { + // First try to get as double + if doubleVal := value.GetDoubleValue(); doubleVal != 0 { + return doubleVal + } + // Try to parse from string (Claude Code sends some values as strings) + if strVal := value.GetStringValue(); strVal != "" { + if parsed, err := strconv.ParseFloat(strVal, 64); err == nil { + return parsed + } + } + return 0 +} + // applyMetricAttribute applies an attribute to a metric func applyMetricAttribute(metric *model.CCOtelMetric, attr *commonv1.KeyValue, metricType string) { key := attr.GetKey() @@ -411,5 +517,27 @@ func applyMetricAttribute(metric *model.CCOtelMetric, attr *commonv1.KeyValue, m metric.Decision = value.GetStringValue() case "language": metric.Language = value.GetStringValue() + // Resource attributes at data point level - apply them (override if already set from resource) + case "session.id": + metric.SessionID = value.GetStringValue() + case "user.account_uuid": + metric.UserAccountUUID = value.GetStringValue() + case "organization.id": + metric.OrganizationID = value.GetStringValue() + case "terminal.type": + metric.TerminalType = value.GetStringValue() + case "app.version": + metric.AppVersion = value.GetStringValue() + case "os.type": + metric.OSType = value.GetStringValue() + case "os.version": + metric.OSVersion = value.GetStringValue() + case "host.arch": + metric.HostArch = value.GetStringValue() + // Additional identifiers at data point level + case "user.id": + metric.UserID = value.GetStringValue() + case "user.email": + metric.UserEmail = value.GetStringValue() } } diff --git a/model/ccotel_types.go b/model/ccotel_types.go index 4bd5d48..c55c0e2 100644 --- a/model/ccotel_types.go +++ b/model/ccotel_types.go @@ -1,48 +1,46 @@ package model // CCOtelRequest is the main request to POST /api/v1/cc/otel +// Flat structure without session - resource attributes are embedded in each metric/event type CCOtelRequest struct { Host string `json:"host"` Project string `json:"project"` - Session *CCOtelSession `json:"session"` Events []CCOtelEvent `json:"events,omitempty"` Metrics []CCOtelMetric `json:"metrics,omitempty"` } -// CCOtelSession represents session data for Claude Code OTEL tracking -type CCOtelSession struct { - SessionID string `json:"sessionId"` - AppVersion string `json:"appVersion"` - OrganizationID string `json:"organizationId,omitempty"` - UserAccountUUID string `json:"userAccountUuid,omitempty"` - TerminalType string `json:"terminalType"` - ServiceVersion string `json:"serviceVersion"` - OSType string `json:"osType"` - OSVersion string `json:"osVersion"` - HostArch string `json:"hostArch"` - WSLVersion string `json:"wslVersion,omitempty"` - StartedAt int64 `json:"startedAt"` - EndedAt int64 `json:"endedAt,omitempty"` - ActiveTimeSeconds int `json:"activeTimeSeconds,omitempty"` - TotalPrompts int `json:"totalPrompts,omitempty"` - TotalToolCalls int `json:"totalToolCalls,omitempty"` - TotalApiRequests int `json:"totalApiRequests,omitempty"` - TotalCostUSD float64 `json:"totalCostUsd,omitempty"` - LinesAdded int `json:"linesAdded,omitempty"` - LinesRemoved int `json:"linesRemoved,omitempty"` - CommitsCreated int `json:"commitsCreated,omitempty"` - PRsCreated int `json:"prsCreated,omitempty"` - TotalInputTokens int64 `json:"totalInputTokens,omitempty"` - TotalOutputTokens int64 `json:"totalOutputTokens,omitempty"` - TotalCacheReadTokens int64 `json:"totalCacheReadTokens,omitempty"` - TotalCacheCreationTokens int64 `json:"totalCacheCreationTokens,omitempty"` +// CCOtelResourceAttributes contains common resource-level attributes +// extracted from OTEL resources and embedded into each metric/event +type CCOtelResourceAttributes struct { + // Standard resource attributes + SessionID string + UserAccountUUID string + OrganizationID string + TerminalType string + AppVersion string + ServiceVersion string + OSType string + OSVersion string + HostArch string + WSLVersion string + + // Additional attributes from data points + UserID string // from user.id (hashed identifier) + UserEmail string // from user.email + + // Custom resource attributes (from OTEL_RESOURCE_ATTRIBUTES) + UserName string // from user.name + MachineName string // from machine.name + TeamID string // from team.id } // CCOtelEvent represents an event from Claude Code (api_request, tool_result, etc.) +// with embedded resource attributes for a flat, session-less structure type CCOtelEvent struct { EventID string `json:"eventId"` EventType string `json:"eventType"` Timestamp int64 `json:"timestamp"` + EventTimestamp string `json:"eventTimestamp,omitempty"` // ISO 8601 timestamp Model string `json:"model,omitempty"` CostUSD float64 `json:"costUsd,omitempty"` DurationMs int `json:"durationMs,omitempty"` @@ -61,9 +59,29 @@ type CCOtelEvent struct { StatusCode int `json:"statusCode,omitempty"` Attempt int `json:"attempt,omitempty"` Language string `json:"language,omitempty"` + + // Embedded resource attributes (previously in session) + SessionID string `json:"sessionId,omitempty"` + UserAccountUUID string `json:"userAccountUuid,omitempty"` + OrganizationID string `json:"organizationId,omitempty"` + TerminalType string `json:"terminalType,omitempty"` + AppVersion string `json:"appVersion,omitempty"` + OSType string `json:"osType,omitempty"` + OSVersion string `json:"osVersion,omitempty"` + HostArch string `json:"hostArch,omitempty"` + + // Additional identifiers + UserID string `json:"userId,omitempty"` + UserEmail string `json:"userEmail,omitempty"` + + // Custom resource attributes + UserName string `json:"userName,omitempty"` + MachineName string `json:"machineName,omitempty"` + TeamID string `json:"teamId,omitempty"` } // CCOtelMetric represents a metric data point from Claude Code +// with embedded resource attributes for a flat, session-less structure type CCOtelMetric struct { MetricID string `json:"metricId"` MetricType string `json:"metricType"` @@ -75,12 +93,30 @@ type CCOtelMetric struct { Tool string `json:"tool,omitempty"` Decision string `json:"decision,omitempty"` Language string `json:"language,omitempty"` + + // Embedded resource attributes (previously in session) + SessionID string `json:"sessionId,omitempty"` + UserAccountUUID string `json:"userAccountUuid,omitempty"` + OrganizationID string `json:"organizationId,omitempty"` + TerminalType string `json:"terminalType,omitempty"` + AppVersion string `json:"appVersion,omitempty"` + OSType string `json:"osType,omitempty"` + OSVersion string `json:"osVersion,omitempty"` + HostArch string `json:"hostArch,omitempty"` + + // Additional identifiers + UserID string `json:"userId,omitempty"` + UserEmail string `json:"userEmail,omitempty"` + + // Custom resource attributes + UserName string `json:"userName,omitempty"` + MachineName string `json:"machineName,omitempty"` + TeamID string `json:"teamId,omitempty"` } // CCOtelResponse is the response from POST /api/v1/cc/otel type CCOtelResponse struct { Success bool `json:"success"` - SessionID int64 `json:"sessionId,omitempty"` EventsProcessed int `json:"eventsProcessed"` MetricsProcessed int `json:"metricsProcessed"` Message string `json:"message,omitempty"` From b71866762f2af3b037a9ae33204e3af937823d99 Mon Sep 17 00:00:00 2001 From: AnnatarHe Date: Sat, 20 Dec 2025 01:30:49 +0800 Subject: [PATCH 2/3] feat(otel): add pwd (present working directory) to resource attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add pwd field extraction from resource attributes and propagate it to both metrics and events for better context tracking. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- daemon/ccotel_processor.go | 4 ++++ model/ccotel_types.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/daemon/ccotel_processor.go b/daemon/ccotel_processor.go index 5971a10..a4d962e 100644 --- a/daemon/ccotel_processor.go +++ b/daemon/ccotel_processor.go @@ -202,6 +202,8 @@ func extractResourceAttributes(resource *resourcev1.Resource) *model.CCOtelResou attrs.MachineName = value.GetStringValue() case "team.id": attrs.TeamID = value.GetStringValue() + case "pwd": + attrs.Pwd = value.GetStringValue() } } @@ -228,6 +230,7 @@ func applyResourceAttributesToMetric(metric *model.CCOtelMetric, attrs *model.CC metric.UserName = attrs.UserName metric.MachineName = attrs.MachineName metric.TeamID = attrs.TeamID + metric.Pwd = attrs.Pwd } // applyResourceAttributesToEvent copies resource attributes into an event @@ -250,6 +253,7 @@ func applyResourceAttributesToEvent(event *model.CCOtelEvent, attrs *model.CCOte event.UserName = attrs.UserName event.MachineName = attrs.MachineName event.TeamID = attrs.TeamID + event.Pwd = attrs.Pwd } // detectProject extracts project from resource attributes or environment diff --git a/model/ccotel_types.go b/model/ccotel_types.go index c55c0e2..f7ba7ba 100644 --- a/model/ccotel_types.go +++ b/model/ccotel_types.go @@ -32,6 +32,7 @@ type CCOtelResourceAttributes struct { UserName string // from user.name MachineName string // from machine.name TeamID string // from team.id + Pwd string // from pwd } // CCOtelEvent represents an event from Claude Code (api_request, tool_result, etc.) @@ -78,6 +79,7 @@ type CCOtelEvent struct { UserName string `json:"userName,omitempty"` MachineName string `json:"machineName,omitempty"` TeamID string `json:"teamId,omitempty"` + Pwd string `json:"pwd,omitempty"` } // CCOtelMetric represents a metric data point from Claude Code @@ -112,6 +114,7 @@ type CCOtelMetric struct { UserName string `json:"userName,omitempty"` MachineName string `json:"machineName,omitempty"` TeamID string `json:"teamId,omitempty"` + Pwd string `json:"pwd,omitempty"` } // CCOtelResponse is the response from POST /api/v1/cc/otel From 38a5570ec651395d85a23f8416e5f373c5a827a0 Mon Sep 17 00:00:00 2001 From: AnnatarHe Date: Sat, 20 Dec 2025 01:35:35 +0800 Subject: [PATCH 3/3] feat(ccotel): add pwd to OTEL_RESOURCE_ATTRIBUTES MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- model/ccotel_env.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/model/ccotel_env.go b/model/ccotel_env.go index e3bed80..ae3df15 100644 --- a/model/ccotel_env.go +++ b/model/ccotel_env.go @@ -121,7 +121,7 @@ func NewBashCCOtelEnvService() CCOtelEnvService { "export OTEL_METRICS_INCLUDE_SESSION_ID=true", "export OTEL_METRICS_INCLUDE_VERSION=true", "export OTEL_METRICS_INCLUDE_ACCOUNT_UUID=true", - "export OTEL_RESOURCE_ATTRIBUTES=\"user.name=$(whoami),machine.name=$(hostname),team.id=shelltime\"", + "export OTEL_RESOURCE_ATTRIBUTES=\"user.name=$(whoami),machine.name=$(hostname),team.id=shelltime,pwd=$(pwd)\"", ccOtelMarkerEnd, } @@ -213,7 +213,7 @@ func NewZshCCOtelEnvService() CCOtelEnvService { "export OTEL_METRICS_INCLUDE_SESSION_ID=true", "export OTEL_METRICS_INCLUDE_VERSION=true", "export OTEL_METRICS_INCLUDE_ACCOUNT_UUID=true", - "export OTEL_RESOURCE_ATTRIBUTES=\"user.name=$(whoami),machine.name=$(hostname),team.id=shelltime\"", + "export OTEL_RESOURCE_ATTRIBUTES=\"user.name=$(whoami),machine.name=$(hostname),team.id=shelltime,pwd=$(pwd)\"", ccOtelMarkerEnd, } @@ -302,7 +302,7 @@ func NewFishCCOtelEnvService() CCOtelEnvService { "set -gx OTEL_METRICS_INCLUDE_SESSION_ID true", "set -gx OTEL_METRICS_INCLUDE_VERSION true", "set -gx OTEL_METRICS_INCLUDE_ACCOUNT_UUID true", - "set -gx OTEL_RESOURCE_ATTRIBUTES \"user.name=$(whoami),machine.name=$(hostname),team.id=shelltime\"", + "set -gx OTEL_RESOURCE_ATTRIBUTES \"user.name=$(whoami),machine.name=$(hostname),team.id=shelltime,pwd=$(pwd)\"", ccOtelMarkerEnd, }