Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Resources.Process/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

* Extended `ProcessDetector` to include `process.executable.path`,
`process.working_directory`, `process.args_count`,`process.creation.time`,
`process.executable.name`, `process.interactive` as well as `process.title`
as per the latest OpenTelemetry Semantic Convention resource/entity definition.
([#2971](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3347))

* Add support for .NET 10.0.
([#2822](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/2822))

Expand Down
38 changes: 30 additions & 8 deletions src/OpenTelemetry.Resources.Process/ProcessDetector.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.ComponentModel;

namespace OpenTelemetry.Resources.Process;

/// <summary>
Expand All @@ -14,20 +16,40 @@ internal sealed class ProcessDetector : IResourceDetector
/// <returns>Resource with key-value pairs of resource attributes.</returns>
public Resource Detect()
{
return new Resource(new List<KeyValuePair<string, object>>(2)
using var currentProcess = System.Diagnostics.Process.GetCurrentProcess();

if (currentProcess.HasExited)
{
return Resource.Empty;
}

var attributes = new List<KeyValuePair<string, object>>(9)
{
new(ProcessSemanticConventions.AttributeProcessOwner, Environment.UserName),
new(ProcessSemanticConventions.AttributeProcessArgsCount, Environment.GetCommandLineArgs().Length),
new(ProcessSemanticConventions.AttributeProcessTitle, currentProcess.MainWindowTitle),
new(ProcessSemanticConventions.AttributeProcessWorkingDir, Environment.CurrentDirectory),

new(ProcessSemanticConventions.AttributeProcessExecName, currentProcess.ProcessName),
new(ProcessSemanticConventions.AttributeProcessInteractive, Environment.UserInteractive),
#if NET
new(ProcessSemanticConventions.AttributeProcessPid, Environment.ProcessId),
});
new(ProcessSemanticConventions.AttributeProcessExecPath, Environment.ProcessPath ?? string.Empty),
};
#else
new(ProcessSemanticConventions.AttributeProcessPid, GetProcessPid()),
});
static int GetProcessPid()
new(ProcessSemanticConventions.AttributeProcessPid, currentProcess.Id),
};
#endif

try
{
using var process = System.Diagnostics.Process.GetCurrentProcess();
return process.Id;
attributes.Add(new(ProcessSemanticConventions.AttributeProcessStartTime, currentProcess.StartTime.ToString("O") ?? DateTime.Now.ToString("O")));
}
#endif
catch (Win32Exception)
{
attributes.Add(new(ProcessSemanticConventions.AttributeProcessStartTime, DateTime.Now.ToString("O")));
}
Comment on lines +46 to +51
Copy link
Member

Choose a reason for hiding this comment

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

Fallbacks here are not true IMO. If you are not detect attributes, just omit it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about that but in the case here creation time is an identifying attribute and all identifying should actually be required hence open-telemetry/weaver#986 to address it.

Also Using The current time enables the functionality of distinguishing between telemetry from process starts, leaving it out you lose that.

Copy link
Member

Choose a reason for hiding this comment

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

Lack of the data is better than the wrong data.

Copy link
Contributor Author

@thompson-tomo thompson-tomo Nov 4, 2025

Choose a reason for hiding this comment

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

Are you saying that all the process entity attributes should be omitted if the creation time can not be set given it is an identifying attribute?

For me the exact value of the atrribute is not as meaningful as having a value there which can then be used to achieve the use case.

Copy link
Member

Choose a reason for hiding this comment

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

I am not against keeping part of the attributes. Part of data seems to be fine. I am against putting obviously wrong data in the attributes/any other signal.

In this case, add this attribute conditionally if it is available. You can document it properly in the README. Small deviations from the semantics should be allowed if we have technical blockers for implementation.


return new Resource(attributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,11 @@ internal static class ProcessSemanticConventions
{
public const string AttributeProcessOwner = "process.owner";
public const string AttributeProcessPid = "process.pid";
public const string AttributeProcessExecPath = "process.executable.path";
public const string AttributeProcessWorkingDir = "process.working_directory";
public const string AttributeProcessArgsCount = "process.args_count";
public const string AttributeProcessStartTime = "process.creation.time";
public const string AttributeProcessExecName = "process.executable.name";
public const string AttributeProcessInteractive = "process.interactive";
public const string AttributeProcessTitle = "process.title";
}
4 changes: 3 additions & 1 deletion src/OpenTelemetry.Resources.Process/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ using var loggerFactory = LoggerFactory.Create(builder =>
The resource detectors will record the following metadata based on where
your application is running:

- **ProcessDetector**: `process.owner`, `process.pid`.
- **ProcessDetector**: `process.owner`, `process.pid`, `process.executable.path`,
`process.working_directory`, `process.args_count`,`process.creation.time`,
`process.executable.name`, `process.interactive`, `process.title`.

## References

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,23 @@ public void TestProcessAttributes()
var resource = ResourceBuilder.CreateEmpty().AddProcessDetector().Build();

var resourceAttributes = resource.Attributes.ToDictionary(x => x.Key, x => x.Value);
#if NET
Assert.Equal(9, resourceAttributes.Count);
#else
Assert.Equal(8, resourceAttributes.Count);
#endif

Assert.Equal(2, resourceAttributes.Count);

Assert.IsType<long>(resourceAttributes[ProcessSemanticConventions.AttributeProcessArgsCount]);
Assert.IsType<string>(resourceAttributes[ProcessSemanticConventions.AttributeProcessExecName]);
#if NET
Assert.IsType<string>(resourceAttributes[ProcessSemanticConventions.AttributeProcessExecPath]);
#endif
Assert.IsType<bool>(resourceAttributes[ProcessSemanticConventions.AttributeProcessInteractive]);
Assert.IsType<string>(resourceAttributes[ProcessSemanticConventions.AttributeProcessOwner]);
Assert.IsType<long>(resourceAttributes[ProcessSemanticConventions.AttributeProcessPid]);

Assert.IsType<string>(resourceAttributes[ProcessSemanticConventions.AttributeProcessStartTime]);
Assert.IsType<string>(resourceAttributes[ProcessSemanticConventions.AttributeProcessTitle]);
Assert.IsType<string>(resourceAttributes[ProcessSemanticConventions.AttributeProcessWorkingDir]);
}
}