Agent instructions for the Jenkins OpenTelemetry Plugin.
Artifact: io.jenkins.plugins:opentelemetry
Type: Jenkins HPI Plugin (.hpi packaging)
Language: Java 21
Build Tool: Maven
Purpose: Monitor and observe Jenkins with OpenTelemetry. Provides distributed tracing for pipeline executions, HTTP requests, metrics collection, and log storage integration with observability backends (Elastic, Jaeger, Grafana, Dynatrace).
Note: Version information for dependencies can be found in pom.xml.
Follow these principles when making changes:
- KISS (Keep It Simple, Stupid): Straightforward implementations without over-engineering
- TDA (Tell, Don't Ask): Objects encapsulate behavior and tell what to do rather than exposing state
- YAGNI (You Aren't Gonna Need It): Only implement features that are actually needed
- DRY (Don't Repeat Yourself): Reuse code through well-defined abstractions
- Structured Programming: Clear control flow with minimal complexity
- Java 21 or later
- Maven 3.8.x or later
./mvnw clean install./mvnw hpi:runThis starts Jenkins on http://localhost:8080/jenkins with the plugin loaded.
cd demos
make start-elastic # Elastic Stack (Elasticsearch, Kibana, APM)
make start-grafana # Grafana Stack (Tempo, Loki)
make start # Base stack
make stop # Stop all containers# Check code formatting
./mvnw spotless:check
# Apply code formatting
./mvnw spotless:apply# Run SpotBugs analysis
./mvnw spotbugs:check
# Run Error Prone analysis
./mvnw clean verify -P error-prone-check- Use
@NonNulland@CheckForNullannotations from FindBugs - Never return null; use
Optionalfor potentially absent values - Use
@Extensionannotation for Jenkins extension points - Mark optional extensions with
@Extension(optional = true, dynamicLoadable = YesNoMaybe.YES) - Prefer composition over inheritance
- Use interface-based design for extensibility
- Keep classes focused on a single responsibility (Single Responsibility Principle)
- Classes should be small: aim for fewer than 200 lines when possible
- Methods should be concise: aim for fewer than 20 lines
- Limit method parameters to 3-4; use parameter objects for more complex cases
- Group related functionality into cohesive packages
- Classes/Interfaces: Use
PascalCase(e.g.,MonitoringRunListener) - Methods/Variables: Use
camelCase(e.g.,createSpanBuilder) - Constants: Use
UPPER_SNAKE_CASE(e.g.,MAX_RETRY_ATTEMPTS) - Packages: Use lowercase, concatenated words (e.g.,
io.jenkins.plugins.opentelemetry) - Use meaningful, descriptive names that reveal intent
- Avoid abbreviations unless widely recognized (e.g.,
HTTP,URL) - Boolean variables/methods should indicate true/false (e.g.,
isEnabled,hasPermission)
- Catch specific exceptions rather than generic
Exception - Never catch
ThrowableorErrorunless absolutely necessary - Don't ignore exceptions; at minimum, log them
- Use try-with-resources for
AutoCloseableresources - Prefer checked exceptions for recoverable conditions
- Document exceptions with
@throwsin Javadoc
- Prefer
Optional<T>over returning null for methods that may not have a value - Use
@NonNulland@CheckForNullannotations consistently - Validate method parameters at the beginning of methods
- Avoid passing null as arguments; use overloaded methods instead
- Initialize collections to empty rather than null
- Prefer immutable collections when possible (e.g.,
List.of(),Set.of()) - Use
Collections.unmodifiableList()for defensive copies - Use streams for data processing, but keep pipelines readable (max 3-4 operations)
- Prefer method references over lambdas when possible (e.g.,
String::length) - Use appropriate collection types:
Listfor ordered,Setfor uniqueness,Mapfor key-value
- Avoid premature optimization; measure before optimizing
- Use
StringBuilderfor string concatenation in loops - Cache expensive computations when appropriate
- Be mindful of boxing/unboxing with primitives in collections
- Use lazy initialization for expensive objects when appropriate
- Prefer primitive types over wrapper classes when nullability isn't needed
- Minimize mutable shared state
- Use
java.util.concurrentclasses over raw synchronization - Prefer
ExecutorServiceover creating threads directly - Document thread-safety guarantees in class Javadoc
- Use
volatilefor flags that need visibility guarantees - Avoid
synchronizedon public methods; use private locks instead
- Write tests for all public APIs
- Use meaningful test method names that describe what is being tested
- Follow Arrange-Act-Assert (AAA) pattern
- Keep tests focused: one logical assertion per test
- Use
@Test(expected = Exception.class)orassertThrows()for exception testing - Mock external dependencies; test units in isolation
- Write self-documenting code; let code explain "what" and comments explain "why"
- Document all public APIs with Javadoc
- Include examples in Javadoc for complex methods
- Keep comments up-to-date; outdated comments are worse than no comments
- Avoid obvious comments that restate the code
- Use
TODOandFIXMEtags for pending work, with issue references
- Use records for immutable data carriers
- Use sealed classes for restricted inheritance hierarchies
- Use pattern matching where applicable (instanceof, switch)
- Use text blocks for multi-line strings
- Use var for local variables when type is obvious from context
- Use enhanced switch expressions with yield
- Indentation: 4 spaces for Java, 2 spaces for YAML/JSON
- Line length: Maximum 120 characters
- End of line: LF (Unix style)
- Charset: UTF-8
- Trailing whitespace: Removed in Java files
- Final newline: Not required in most files, required in YAML/JSON/shell scripts
- Always run
./mvnw spotless:applybefore committing
- All code must pass SpotBugs checks without warnings
- Use FindBugs annotations to document nullability:
@NonNull- Parameter/return value cannot be null@CheckForNull- May return null (prefer Optional instead)@SuppressFBWarnings- Only when necessary with justification
- Run
./mvnw spotbugs:checkto verify before committing
All handlers must implement:
@Override
public boolean canCreateSpanBuilder(@NonNull Run<?, ?> run) {
// Check capability
}
@Override
public int ordinal() {
return 100; // Lower executes first
}
@Override
public int compareTo(OtherHandler other) {
if (this.ordinal() == other.ordinal()) {
return this.getClass().getName().compareTo(other.getClass().getName());
}
return Integer.compare(this.ordinal(), other.ordinal());
}- Listener classes end with
Listener(e.g.,MonitoringRunListener) - Handler classes end with
Handler(e.g.,RunHandler,CauseHandler) - Backend classes end with
Backend(e.g.,ElasticBackend) - Use semantic attribute names from OpenTelemetry conventions
- Configuration classes use
Configurationsuffix
Bad (Asking):
if (run.getResult() == Result.FAILURE) {
span.setAttribute("status", "failed");
}Good (Telling):
span.setStatus(StatusCode.ERROR);When editing documentation files, follow these markdown conventions:
- Use ATX-style headings (
#,##,###) not underline style - One H1 (
#) per document for the title - Don't skip heading levels (e.g., don't jump from
##to####) - Add blank line before and after headings
- Use sentence case for headings, not title case
- Always specify language for syntax highlighting:
public class Example {}
- Use
bashfor shell commands, notshorshell - Use inline code (backticks) for: class names, method names, file paths, commands, property names
- Examples:
MonitoringRunListener,pom.xml,./mvnw verify
- Use descriptive link text, not "click here" or URLs
- Good:
[OpenTelemetry Specification](https://opentelemetry.io/docs/specs/otel/) - Bad:
Click [here](url)or bare URLs - Use relative paths for internal documentation links
- Use
-for unordered lists (consistent bullet style) - Add blank line before and after lists
- Indent nested lists with 2 spaces
- Use
1.for ordered lists (number will auto-increment) - Keep list items parallel in structure
- Use
**bold**for UI elements, important terms, emphasis - Use
*italic*sparingly for subtle emphasis or citations - Don't use bold for code elements; use backticks instead
- Aim for 120 characters maximum per line (matching code style)
- Break at sentence boundaries when possible
- Exception: long URLs, code blocks, or tables
- Align columns for readability in source
- Include header separator row with at least 3 dashes per column
- Example:
| Column 1 | Column 2 | |----------|----------| | Value 1 | Value 2 |
- One blank line between sections
- Two blank lines before top-level (
##) headings for visual separation - No trailing whitespace
- File should end with single newline
Use Mermaid for creating diagrams in documentation. Mermaid diagrams are defined in code blocks and render as visual diagrams.
- Architecture diagrams showing component relationships
- Sequence diagrams for interaction flows
- Flowcharts for process logic
- State diagrams for system states
- Entity relationship diagrams for data models
Flowchart - Component relationships and architecture:
flowchart TB
A[Component A] --> B[Component B]
A --> C[Component C]
B --> D[Backend]
Sequence Diagram - Interaction flows and timing:
sequenceDiagram
participant Client
participant Server
Client->>Server: Request
Server-->>Client: Response
Class Diagram - Object-oriented structure:
classDiagram
class Handler {
+canCreateSpanBuilder()
+ordinal()
}
Handler <|-- RunHandler
Handler <|-- StepHandler
- Keep diagrams focused: one purpose per diagram
- Use descriptive labels for all elements
- Limit complexity: maximum 7-10 elements per diagram
- Use consistent naming with codebase (e.g., class names match actual classes)
- Add direction hints:
TB(top-bottom),LR(left-right) - Use quotes for labels with special characters:
["Label with: special"] - Prefer flowcharts for architecture, sequence diagrams for interactions
- Test diagrams render correctly in GitHub/GitLab preview
- Use square brackets
[]for default boxes - Use parentheses
()for rounded boxes - Use double parentheses
(())for circles - Use curly braces
{}for diamonds (decisions) - Use
-->for solid arrows,-.->for dashed arrows - Use
-->>for return/response flows in sequence diagrams
flowchart LR
Request[HTTP Request] --> Filter[Servlet Filter]
Filter --> Handler[Request Handler]
Handler --> Response[HTTP Response]
- Mermaid Documentation
- Mermaid Live Editor for testing diagrams
When editing YAML files (JCasC, workflows, configurations), follow these conventions:
- Use 2 spaces for indentation (never tabs)
- Keep consistent indentation throughout the file
- Align nested elements properly
- Use blank lines to separate logical sections
- Use lowercase for:
true,false,null(notTrue,FALSE,~) - Quote strings containing special characters:
:,{,},[,],,,&,*,#,?,|,-,<,>,=,!,%,@,` - Quote strings that could be interpreted as numbers or booleans:
"1.0","yes","no" - Use multiline strings with
|(literal) or>(folded) for long text:description: | This is a multiline description that preserves line breaks.
- Use
-for list items with consistent indentation - Prefer flow style
[item1, item2]only for short, simple lists - Keep complex items in block style:
dependencies: - groupId: io.opentelemetry artifactId: opentelemetry-api - groupId: io.opentelemetry artifactId: opentelemetry-sdk
- Use explicit keys without quotes when possible
- Order keys logically (e.g., name/id first, config after)
- Group related configuration together
- Use
#for comments with a space after:# This is a comment - Add comments above the line they describe, not inline
- Use comments to explain non-obvious configuration choices
- Keep files under 500 lines; split large configs into multiple files
- Validate YAML syntax before committing (use
yamllintor IDE validation) - Avoid anchors (
&) and aliases (*) unless necessary for DRY - Don't use complex multiline keys or values when simple structures suffice
- Ensure sensitive values use Jenkins credentials, not plain text
# OpenTelemetry configuration for Jenkins
otel:
endpoint: "http://localhost:4317"
protocol: grpc
authentication:
type: bearer
credentials: ${OTEL_TOKEN}
exporters:
- type: otlp
enabled: true
timeout: 30When creating or updating technical documentation, follow these principles:
- Correct: Documentation must be accurate and free from errors. Wrong documentation is worse than no documentation
- Current: Keep documentation up-to-date with code changes. Regular reviews are essential
- Understandable: Write for the target audience. Use clear language, explain reasons, provide context
- Relevant: Focus on task-oriented content that stakeholders actually need
- Referenceable: Number sections, diagrams, and tables consistently (e.g., "Fig. 3.1", "Table 2.1")
- Easy to find: Organize top-down, use templates (like arc42), provide table of contents with hyperlinks
- Maintainable: Abstract away details when possible, avoid documenting every implementation detail
- Version controlled: All documentation artifacts should be in version control (Git)
- Continuously updated: Document incrementally, include documentation in Definition of Done
- Use appropriate tools: Prefer established tools over new ones to reduce maintenance burden
- Use active voice and positive statements (avoid negation)
- Keep sentences concise (15-20 words average)
- Correct spelling and grammar are essential
- Combine diagrams with explanatory text
- Provide glossary for specific terms
- Explain reasons behind decisions, not just what was decided
- Leave out obvious or low-value details
- Abstract: show aggregations instead of all elements
- Document what you can promise to maintain
- Regularly delete outdated content
- Keep diagram source files under version control
- Automation helps with publishing, but not with content creation
authentication/- OTLP authentication mechanismsbackend/- Observability backend integrationscomputer/- Jenkins agent monitoringinit/- Plugin initializationjob/- Job and pipeline monitoringjob/cause/- Build trigger handlersjob/runhandler/- Run type handlersjob/step/- Pipeline step handlersjob/log/- Build log management
queue/- Build queue monitoringsecurity/- Security event monitoringsemconv/- Semantic conventionsservlet/- HTTP request tracing
./mvnw clean verify./mvnw test -Dtest=MonitoringRunListenerTest./mvnw verify -pl :opentelemetry -Pintegration-tests- Unit tests use Mockito for Jenkins API mocking
- Integration tests use Testcontainers for Elastic/Jaeger
- Pipeline tests use Jenkins test harness
- All handler implementations must have tests
- Tests must verify OpenTelemetry span/metric creation
When adding new features:
- Add unit tests for the component
- Add integration tests if it involves external systems
- Verify spans are created with correct attributes
- Test error cases and edge conditions
- Run
./mvnw verifybefore committing
ExtendedGitSampleRepoRule- Git repository testingElasticStack- Testcontainers for Elastic- Configuration as Code test harness
- Format code:
./mvnw spotless:apply - Check formatting:
./mvnw spotless:check - Run static analysis:
./mvnw spotbugs:check - Run full test suite:
./mvnw clean verify - Run Error Prone:
./mvnw clean verify -P error-prone-check - Verify all tests pass
- Update documentation if adding features
- Add changelog entry if applicable
[JENKINS-XXXXX] Brief description of change
Or for minor changes without JIRA:
Fix typo in MonitoringRunListener
- What problem does this solve?
- How to test the change
- Any breaking changes
- Screenshots for UI changes
- Link to JIRA issue if applicable
Follow conventional commits:
feat: add support for custom trace attributes
fix: correct span timing for parallel branches
docs: update AGENTS.md with testing instructions
test: add integration tests for Grafana backend
- All handlers must follow the ordinal pattern
- Code must pass Spotless, SpotBugs, and Error Prone checks
- Proper use of
@NonNulland@CheckForNullannotations - No new dependencies without justification
- Security considerations documented
- Performance impact assessed
- Backward compatibility maintained
Event-driven architecture using Jenkins extension points and OpenTelemetry SDK.
flowchart TB
Jenkins["Jenkins Core"] --> Listeners["Plugin Listeners"]
Listeners --> Handlers["Extensible Handlers"]
Handlers --> SDK["OpenTelemetry SDK"]
SDK --> Collector["OTLP Collector"]
Collector --> Backends["Observability Backends"]
- RunListener →
MonitoringRunListener- Build lifecycle events - GraphListener →
GraphListenerAdapterToPipelineListener- Pipeline events - ServletFilter →
TraceContextServletFilter- HTTP tracing - ComputerListener →
MonitoringComputerListener- Agent events - QueueListener →
MonitoringQueueListener- Queue events - SecurityListener →
AuditingSecurityListener- Security events
JenkinsOpenTelemetryPluginConfiguration- Global config, OTLP endpoint, authenticationJenkinsControllerOpenTelemetry- SDK lifecycle singletonOpenTelemetryConfiguration- Properties, environment variables
Extend OtlpAuthentication: NoAuthentication, BearerTokenAuthentication, HeaderAuthentication
Extend ObservabilityBackend: ElasticBackend, JaegerBackend, ZipkinBackend, GrafanaBackend, DynatraceBackend
- Job Monitoring:
MonitoringRunListenerdelegates toRunHandlerimplementations - Pipeline Monitoring:
MonitoringPipelineListenerimplementsPipelineListenerinterface - HTTP Tracing:
TraceContextServletFilter,StaplerInstrumentationServletFilter - Agent Monitoring:
MonitoringComputerListener,MonitoringCloudListener - Queue Monitoring:
MonitoringQueueListener - Security Monitoring:
AuditingSecurityListener - Log Management:
OtelLogSenderBuildListener,OtelLogOutputStream
All handlers implement:
isSupported()/canCreateSpanBuilder()- Check if handler appliesordinal()- Execution order (lower runs first)configure(ConfigProperties)- Optional configurationcompareTo()- For deterministic ordering
RunHandler - Different run types (Pipeline, Freestyle, Maven)
@Extension(optional = true)
public class MyRunHandler implements RunHandler {
@Override
public boolean canCreateSpanBuilder(@NonNull Run<?, ?> run) {
return run instanceof MyRunType;
}
@Override
public SpanBuilder createSpanBuilder(@NonNull Run<?, ?> run, @NonNull Tracer tracer) {
return tracer.spanBuilder("jenkins.run.my_type")
.setAttribute("my.attribute", value);
}
@Override
public int ordinal() { return 100; }
}StepHandler - Pipeline steps (sh, bat, git)
@Extension
public class MyStepHandler implements StepHandler {
@Override
public boolean canCreateSpanBuilder(@NonNull FlowNode node, @NonNull WorkflowRun run) {
return node instanceof StepAtomNode
&& ((StepAtomNode) node).getDescriptor() instanceof MyStepDescriptor;
}
@Override
public SpanBuilder createSpanBuilder(@NonNull FlowNode node, @NonNull WorkflowRun run, @NonNull Tracer tracer) {
return tracer.spanBuilder("jenkins.step.my_step");
}
}CauseHandler - Build triggers (GitHub, GitLab, User)
@Extension(optional = true)
public class MyCauseHandler implements CauseHandler {
@Override
public boolean isSupported(@NonNull Cause cause) {
return cause instanceof MyCause;
}
@Override
public String getStructuredDescription(@NonNull Cause cause) {
return "MyCause:" + ((MyCause) cause).getId();
}
@Override
public int ordinal() { return 100; }
}io.opentelemetry:opentelemetry-bom- OpenTelemetry API/SDKio.opentelemetry.instrumentation:opentelemetry-instrumentation-bom- Instrumentationio.opentelemetry.semconv:opentelemetry-semconv- Semantic conventionsco.elastic.clients:elasticsearch-java- Elasticsearch client
io.jenkins.plugins:opentelemetry-api- OpenTelemetry wrapperorg.jenkins-ci.plugins.workflow:workflow-*- Pipeline supportorg.jenkins-ci.plugins:credentials- Credential management
org.jenkins-ci.plugins:git- Git SCMorg.jenkins-ci.plugins:github-branch-source- GitHuborg.jenkins-ci.plugins:gitlab-plugin- GitLaborg.jenkins-ci.plugins:bitbucket- Bitbucketorg.jenkins-ci.plugins:job-dsl- Job DSL
- Check if already provided by parent POM
- Use dependency management for version control
- Exclude transitive dependencies that conflict with Jenkins
- Test with minimal Jenkins version (see
pom.xmlfor baseline)
io.jenkins.plugins.opentelemetry/
├── authentication/ # OTLP authentication (NoAuth, Bearer, Header)
├── backend/ # Backend integrations (Elastic, Jaeger, Grafana, etc.)
│ ├── custom/ # Custom backend implementations
│ ├── elastic/ # Elasticsearch log retrieval
│ └── grafana/ # Loki log retrieval
├── computer/ # Agent lifecycle monitoring
├── init/ # Plugin initialization, JUL bridging
├── jenkins/ # Jenkins core integration
├── job/ # Job and pipeline monitoring
│ ├── cause/ # Build trigger handlers (GitHub, GitLab, User, etc.)
│ ├── jenkins/ # Pipeline listeners and adapters
│ ├── log/ # Log capture and export
│ ├── runhandler/ # Run type handlers (Pipeline, Freestyle, etc.)
│ └── step/ # Pipeline step handlers (sh, bat, git, etc.)
├── queue/ # Build queue monitoring
├── security/ # Security event monitoring
├── semconv/ # Semantic conventions (attributes, metrics)
└── servlet/ # HTTP request tracing filters
sequenceDiagram
participant Queue as Job Queue
participant QueueListener as MonitoringQueueListener
participant RunListener as MonitoringRunListener
participant RunHandler as RunHandler
participant PipelineListener as MonitoringPipelineListener
participant GraphAdapter as GraphListenerAdapter
participant StepHandler as StepHandler
participant OTLP as OTLP Backend
Queue->>QueueListener: Job queued
QueueListener->>RunListener: Job started
RunListener->>RunHandler: onStarted()
RunHandler->>OTLP: Create root span
RunListener->>PipelineListener: onStartPipeline()
loop For each stage
GraphAdapter->>PipelineListener: Detect stage node
PipelineListener->>OTLP: onStartStageStep()<br/>Create child span
loop For each step
PipelineListener->>StepHandler: canCreateSpanBuilder()
StepHandler->>StepHandler: createSpanBuilder()
StepHandler->>OTLP: Create child span
end
PipelineListener->>OTLP: onEndStageStep()<br/>Close stage span
end
RunListener->>RunHandler: onCompleted()
RunHandler->>OTLP: Close root span<br/>Record metrics
sequenceDiagram
participant Client as HTTP Client
participant TraceFilter as TraceContextServletFilter
participant StaplerFilter as StaplerInstrumentationServletFilter
participant Jenkins as Jenkins Handler
participant OTLP as OTLP Backend
Client->>TraceFilter: HTTP Request
TraceFilter->>TraceFilter: Extract trace context<br/>(W3C propagation)
TraceFilter->>StaplerFilter: Forward request
StaplerFilter->>OTLP: Create span
StaplerFilter->>Jenkins: Process request
Jenkins-->>StaplerFilter: Response
StaplerFilter->>OTLP: Close span<br/>(with status code)
StaplerFilter-->>Client: HTTP Response
sequenceDiagram
participant Step as Pipeline Step
participant LogStream as OtelLogOutputStream
participant Logger as OTel Logger
participant Context as Trace Context
participant OTLP as OTLP Backend
participant Mirror as TeeBuildListener
participant Jenkins as Jenkins Storage
Step->>LogStream: Write log output
LogStream->>Logger: Convert to LogRecord
LogStream->>Context: Attach trace context
Logger->>OTLP: Export log
alt Log Mirroring Enabled
LogStream->>Mirror: Mirror log
Mirror->>Jenkins: Store in Jenkins
end
- Job queued →
MonitoringQueueListenercaptures queue event - Job started →
MonitoringRunListener.onStarted()creates root span - RunHandler creates span builder for specific run type
- Pipeline starts →
MonitoringPipelineListener.onStartPipeline() - For each stage:
GraphListenerAdapterToPipelineListenerdetects stage nodeonStartStageStep()creates child span- For each step: StepHandler creates span if applicable
onEndStageStep()closes stage span
- Build completes →
MonitoringRunListener.onCompleted()closes root span - Metrics recorded (duration, result, queue time)
OpenTelemetry configuration (set in Jenkins UI or JCasC):
otel.exporter.otlp.endpoint=http://localhost:4317
otel.exporter.otlp.protocol=grpc # or http/protobuf
otel.exporter.otlp.headers=Authorization=Bearer token123
otel.instrumentation.jenkins.agent.enabled=true
otel.instrumentation.jenkins.remoting.enabled=false- Extend
ObservabilityBackendinbackend/ - Implement
getTraceVisualisationUrlTemplate()for trace viewing - Optionally implement log storage retrieval
- Add
@Extensionand@Symbolannotations - Add configuration UI in
src/main/resources/ - Test with demo environment
- Create class implementing
RunHandlerinjob/runhandler/ - Implement
canCreateSpanBuilder()to identify run type - Implement
createSpanBuilder()to create span with attributes - Set appropriate
ordinal()for execution order - Add
@Extension(optional = true)if depends on optional plugin - Add tests verifying span creation
- Create class implementing
StepHandlerinjob/step/ - Implement
canCreateSpanBuilder()to match step type - Implement
createSpanBuilder()to create span - Override
afterSpanCreated()if needed for additional attributes - Add integration tests with pipeline
- Check handler
ordinal()- lower values execute first - Verify
canCreateSpanBuilder()returns true for your case - Add debug logging in handler
- Check OpenTelemetry SDK is initialized:
GlobalOpenTelemetry.get() - Verify OTLP exporter configuration
- Handlers initialized lazily on first use
- OpenTelemetry batches exports asynchronously
- Bounded queues prevent memory exhaustion
- Sampling configurable via OTLP configuration
- Agent instrumentation and remoting are opt-in
- Store sensitive tokens in Jenkins credentials, not plain text
- Use pluggable authentication for OTLP endpoints
- Security listener tracks authentication events
- Filter sensitive data from spans/logs
- Use HTTPS for OTLP communication
See README.md for contribution guidelines. Follow the established patterns:
- Use handler pattern for extensibility
- Follow semantic conventions
- Add tests for new functionality
- Document configuration options
- Update relevant backend integrations