[receiver/akamaisiemreceiver] - Initial implementation of Akamai SIEM native OTEL receiver#1119
Conversation
2547c30 to
2beae84
Compare
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughIntroduces a new Akamai SIEM receiver module that polls the Akamai SIEM API using EdgeGrid-signed requests, streams NDJSON event pages, persists chain cursors to disk, and emits logs in three modes: raw (ECS passthrough), otel (semantic mapping), and dual (both outputs). Adds client, streaming, cursor store, poller state machine, mapping and telemetry code, shared-instance lifecycle, factory integration, metadata/manifest/module files, documentation and benchmarks, extensive unit/integration/tracing tests, testdata, and CODEOWNERS/.gitignore updates. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@receiver/akamaisiemreceiver/factory.go`:
- Around line 77-103: The package-global map seenPartialKeys is accessed
concurrently in createLogsReceiver causing races; protect all accesses to
seenPartialKeys (both the check "if prev, seen := seenPartialKeys[pk]; seen &&
..." and the subsequent assignment seenPartialKeys[pk] = key) with a
package-level mutex (e.g., a sync.RWMutex or sync.Mutex such as
seenPartialKeysMu) so reads/writes are serialized; add the mutex variable next
to seenPartialKeys and wrap the read+write block in the appropriate Lock/Unlock
(or RLock for the read if using RWMutex and upgrade to Lock for the write) to
eliminate concurrent map read/write panics and race conditions involving
partialKey/connectionKey and createLogsReceiver.
In `@receiver/akamaisiemreceiver/go.mod`:
- Line 78: Update the grpc dependency from google.golang.org/grpc v1.79.2 to
v1.79.3 (or later) to address the authorization-bypass vulnerability; edit the
go.mod entry for google.golang.org/grpc to the newer version, then run go get
google.golang.org/grpc@v1.79.3 (or desired newer tag) and go mod tidy to update
go.sum and lock the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3757fe26-f6b6-4c3b-bf5c-73352b3d90ce
⛔ Files ignored due to path filters (13)
receiver/akamaisiemreceiver/go.sumis excluded by!**/*.sumreceiver/akamaisiemreceiver/img/01_eps_by_batch.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/02_eps_by_batch_and_buffer.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/03_rss_by_batch.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/04_alloc_per_event.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/05_cpu_per_event.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/06_sys_memory.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/07_page_processing_time.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/08_dual_mode_comparison.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/architecture_dual.svgis excluded by!**/*.svgreceiver/akamaisiemreceiver/img/architecture_ecs.svgis excluded by!**/*.svgreceiver/akamaisiemreceiver/img/architecture_otel.svgis excluded by!**/*.svgreceiver/akamaisiemreceiver/img/architecture_overview.svgis excluded by!**/*.svg
📒 Files selected for processing (40)
.github/CODEOWNERS.gitignoredistributions/elastic-components/manifest.yamlreceiver/akamaisiemreceiver/BENCHMARKS.mdreceiver/akamaisiemreceiver/Makefilereceiver/akamaisiemreceiver/README.mdreceiver/akamaisiemreceiver/benchmark_test.goreceiver/akamaisiemreceiver/config.goreceiver/akamaisiemreceiver/config_test.goreceiver/akamaisiemreceiver/doc.goreceiver/akamaisiemreceiver/factory.goreceiver/akamaisiemreceiver/generated_component_test.goreceiver/akamaisiemreceiver/generated_package_test.goreceiver/akamaisiemreceiver/go.modreceiver/akamaisiemreceiver/integration_test.goreceiver/akamaisiemreceiver/internal/auth/edgegrid.goreceiver/akamaisiemreceiver/internal/auth/edgegrid_test.goreceiver/akamaisiemreceiver/internal/mapper/benchmark_test.goreceiver/akamaisiemreceiver/internal/mapper/otel.goreceiver/akamaisiemreceiver/internal/mapper/otel_test.goreceiver/akamaisiemreceiver/internal/metadata/generated_status.goreceiver/akamaisiemreceiver/internal/metadata/generated_telemetry.goreceiver/akamaisiemreceiver/internal/sharedcomponent/benchmark_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/client.goreceiver/akamaisiemreceiver/internal/sharedcomponent/client_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/cursor.goreceiver/akamaisiemreceiver/internal/sharedcomponent/cursor_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/poller.goreceiver/akamaisiemreceiver/internal/sharedcomponent/poller_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/shared.goreceiver/akamaisiemreceiver/internal/sharedcomponent/shared_test.goreceiver/akamaisiemreceiver/metadata.yamlreceiver/akamaisiemreceiver/receiver.goreceiver/akamaisiemreceiver/receiver_test.goreceiver/akamaisiemreceiver/testdata/config.yamlreceiver/akamaisiemreceiver/testdata/siem_response.ndjsonreceiver/akamaisiemreceiver/testdata/siem_response_empty.ndjsonreceiver/akamaisiemreceiver/testdata/siem_response_full.ndjsonreceiver/akamaisiemreceiver/testdata/siem_response_no_offset.ndjsonreceiver/akamaisiemreceiver/tracing_test.go
2583aff to
83725d8
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@receiver/akamaisiemreceiver/benchmark_test.go`:
- Line 132: The test is doing a direct type assertion akRcv :=
rcv.(*akamaiReceiver) which will panic because CreateLogs returns a
receiver.Logs backed by a SharedComponent wrapper from LoadOrStore; replace the
factory-backed creation with the existing benchReceiver helper used by the
EmitEvents benchmarks (or refactor the test to call the emitEvents logic
directly) so you get a real *akamaiReceiver instance for testing; locate usages
of CreateLogs, rcv, akRcv and change the setup to use benchReceiver (or extract
emitEvents into a testable function) to avoid asserting the SharedComponent
wrapper to *akamaiReceiver.
In `@receiver/akamaisiemreceiver/internal/auth/edgegrid.go`:
- Around line 116-125: Transport.RoundTrip can panic when t.Base is nil because
http.Client allows a nil Transport; update RoundTrip to use
http.DefaultTransport as a fallback (e.g., set base := t.Base; if base == nil {
base = http.DefaultTransport.(http.RoundTripper) } ) before calling
base.RoundTrip(clone), ensuring you reference Transport.RoundTrip, t.Base, and
http.DefaultTransport when locating and modifying the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 815fbd50-8a07-4fd8-94aa-82da5dedf0f0
⛔ Files ignored due to path filters (13)
receiver/akamaisiemreceiver/go.sumis excluded by!**/*.sumreceiver/akamaisiemreceiver/img/01_eps_by_batch.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/02_eps_by_batch_and_buffer.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/03_rss_by_batch.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/04_alloc_per_event.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/05_cpu_per_event.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/06_sys_memory.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/07_page_processing_time.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/08_dual_mode_comparison.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/architecture_dual.svgis excluded by!**/*.svgreceiver/akamaisiemreceiver/img/architecture_otel.svgis excluded by!**/*.svgreceiver/akamaisiemreceiver/img/architecture_overview.svgis excluded by!**/*.svgreceiver/akamaisiemreceiver/img/architecture_raw.svgis excluded by!**/*.svg
📒 Files selected for processing (40)
.github/CODEOWNERS.gitignoredistributions/elastic-components/manifest.yamlreceiver/akamaisiemreceiver/BENCHMARKS.mdreceiver/akamaisiemreceiver/Makefilereceiver/akamaisiemreceiver/README.mdreceiver/akamaisiemreceiver/benchmark_test.goreceiver/akamaisiemreceiver/config.goreceiver/akamaisiemreceiver/config_test.goreceiver/akamaisiemreceiver/doc.goreceiver/akamaisiemreceiver/factory.goreceiver/akamaisiemreceiver/generated_component_test.goreceiver/akamaisiemreceiver/generated_package_test.goreceiver/akamaisiemreceiver/go.modreceiver/akamaisiemreceiver/integration_test.goreceiver/akamaisiemreceiver/internal/auth/edgegrid.goreceiver/akamaisiemreceiver/internal/auth/edgegrid_test.goreceiver/akamaisiemreceiver/internal/mapper/benchmark_test.goreceiver/akamaisiemreceiver/internal/mapper/otel.goreceiver/akamaisiemreceiver/internal/mapper/otel_test.goreceiver/akamaisiemreceiver/internal/metadata/generated_status.goreceiver/akamaisiemreceiver/internal/metadata/generated_telemetry.goreceiver/akamaisiemreceiver/internal/sharedcomponent/benchmark_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/client.goreceiver/akamaisiemreceiver/internal/sharedcomponent/client_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/cursor.goreceiver/akamaisiemreceiver/internal/sharedcomponent/cursor_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/poller.goreceiver/akamaisiemreceiver/internal/sharedcomponent/poller_test.goreceiver/akamaisiemreceiver/internal/sharedcomponent/shared.goreceiver/akamaisiemreceiver/internal/sharedcomponent/shared_test.goreceiver/akamaisiemreceiver/metadata.yamlreceiver/akamaisiemreceiver/receiver.goreceiver/akamaisiemreceiver/receiver_test.goreceiver/akamaisiemreceiver/testdata/config.yamlreceiver/akamaisiemreceiver/testdata/siem_response.ndjsonreceiver/akamaisiemreceiver/testdata/siem_response_empty.ndjsonreceiver/akamaisiemreceiver/testdata/siem_response_full.ndjsonreceiver/akamaisiemreceiver/testdata/siem_response_no_offset.ndjsonreceiver/akamaisiemreceiver/tracing_test.go
✅ Files skipped from review due to trivial changes (20)
- .gitignore
- receiver/akamaisiemreceiver/testdata/siem_response_empty.ndjson
- .github/CODEOWNERS
- receiver/akamaisiemreceiver/testdata/siem_response_no_offset.ndjson
- distributions/elastic-components/manifest.yaml
- receiver/akamaisiemreceiver/doc.go
- receiver/akamaisiemreceiver/testdata/siem_response.ndjson
- receiver/akamaisiemreceiver/internal/metadata/generated_status.go
- receiver/akamaisiemreceiver/metadata.yaml
- receiver/akamaisiemreceiver/go.mod
- receiver/akamaisiemreceiver/generated_component_test.go
- receiver/akamaisiemreceiver/testdata/siem_response_full.ndjson
- receiver/akamaisiemreceiver/config_test.go
- receiver/akamaisiemreceiver/internal/sharedcomponent/shared_test.go
- receiver/akamaisiemreceiver/README.md
- receiver/akamaisiemreceiver/internal/sharedcomponent/client_test.go
- receiver/akamaisiemreceiver/internal/sharedcomponent/cursor.go
- receiver/akamaisiemreceiver/integration_test.go
- receiver/akamaisiemreceiver/internal/metadata/generated_telemetry.go
- receiver/akamaisiemreceiver/internal/sharedcomponent/poller.go
🚧 Files skipped from review as they are similar to previous changes (4)
- receiver/akamaisiemreceiver/generated_package_test.go
- receiver/akamaisiemreceiver/testdata/config.yaml
- receiver/akamaisiemreceiver/internal/mapper/benchmark_test.go
- receiver/akamaisiemreceiver/config.go
|
@ShourieG this is only for EDOT Collector right? Should it be in the elastic-agent repo? See also #1046 (comment) |
@axw, My Idea was that this receiver should be agent agnostic as in, we will create a component in the Elastic Agent and tie this in as a factory but we also want it to function as an independent receiver that can work with any OTEL backend in headless mode or with a different agent. It's built to work independently and have compatibility with Elastic Agent. |
axw
left a comment
There was a problem hiding this comment.
I've only skimmed, since I was curious about the performance. There's some low hanging fruit for optimising the full otel SemConv configuration.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@receiver/akamaisiemreceiver/README.md`:
- Around line 681-697: The example for receiver "akamai_siem" places the timeout
key at the receiver root but HTTP.Timeout must be under an http: mapping; move
the `timeout: 120s` entry into an `http:` block within the `akamai_siem:` config
(i.e., add an `http:` mapping and place `timeout: 120s` beneath it) so the
`HTTP.Timeout` setting is validated correctly for the akamai_siem receiver
(Scenario 8).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c0518efe-5138-4fe5-9d3e-69983ddfdc12
⛔ Files ignored due to path filters (2)
receiver/akamaisiemreceiver/img/04_alloc_per_event.pngis excluded by!**/*.pngreceiver/akamaisiemreceiver/img/08_dual_mode_comparison.pngis excluded by!**/*.png
📒 Files selected for processing (4)
receiver/akamaisiemreceiver/BENCHMARKS.mdreceiver/akamaisiemreceiver/README.mdreceiver/akamaisiemreceiver/internal/mapper/otel.goreceiver/akamaisiemreceiver/internal/mapper/otel_test.go
|
Hey @axw, whom should I reach out to for starting the review process on this. I was wondering If it was somehow possible to get in before 9.4 but if there are concerns then happy to delay and work on it as necessary. |
|
@ShourieG have you had a review from your team? Probably best to start there, if not. A few high level comments:
|
|
Hey @axw, @andrewkroh wants to review this but he does not seem to have the permission to add himself as a reviewer. |
@axw, Thanks for the feedback. On the Separate PR issue, I can rewrite the commit history into segregated clean commits so a commit by commit review can be done instead. It's already like this to an extent but I can make it more extensive and granular. On the other points -
|
88bb79e to
76ca9ac
Compare
|
@axw, I've addressed all the main outstanding issues besides scraperhelper as it's not compatible with our custom polling and streaming logic and the multiple PR approach. I believe having clean focused granular commits should help the review process, multiple PR's would make it pretty messy due to logic coupling present among components and just add/increase the whole review turn around time. I re-did the commit history to make it more granular for easy reviews. |
847c3a2 to
56a2e5d
Compare
|
@ShourieG sorry for the delay, this dropped off my radar after the long weekend.
I'll look into how we can fix that.
OK. FYI, the reason I ask is because we'll soon have the ability to trigger scrapes with alternative methods other than a simple in-process timer: open-telemetry/opentelemetry-collector#14469 |
@axw I think we can revisit this in future but right now as it is there are some bottlenecks besides a custom polling logic:
Will all these limitations change with the future updates ? |
All valid points. I don't think the dual-output |
| "fmt" | ||
| "time" | ||
|
|
||
| "go.opentelemetry.io/collector/extension/xextension/storage" |
There was a problem hiding this comment.
Have you tested that this works with https://github.com/elastic/beats/tree/main/x-pack/otel/extension/elasticsearchstorage? That's what you would need for compatibility with agentless.
Outside of agentless, users will need to manually configure a filestorage extension, the input won't be stateful by default like it is in Filebeat.
There was a problem hiding this comment.
@cmacknz, Currently it's not compatible with elasticsearchstorage because it lacks a GetClient() method. elasticsearchstorage needs to implement the storage.Extension interface on it's end to actually make it compatible. I can add storage specific code and libs and do something like
switch {
case isStorageExtension(ext):
return ext.(storage.Extension).GetClient(...)
case isBeatsRegistry(ext):
store, _ := ext.(backend.Registry).Access(...)
return &adapter{store}, nil
default:
return nil, fmt.Errorf("unsupported storage type %q", id)
}
but this seems to break the philosophy of OTEL receivers as being storage agnostic. If elasticsearchstorage implements storage.Extension{} the current receiver implementation would work seamlessly without any change/storage specific code.
There was a problem hiding this comment.
I think the complexity comes in how storage extension and the beats statestore handle the actual data, where otel storage extension works with raw []byte, elasticsearchstorage works with interface{} and does custom encoding on the value. I had a brainstorm session with Claude and came up with a plan on how to handle this in Beats.
We can also stick to the approach of breaking convention and having a storage based approach inside the receiver itself. I think that's the general call to take here. If you find the plan somewhat viable, I can create a PR in beats for it.
There was a problem hiding this comment.
Thanks for taking an initial look, the elasticsearch storage extension should be compatible with any receiver we just didn't have that need when we originally adapted it to the storage interface and it was done with some time pressure.
Probably easier to start with a PR if that is easy, then we can see exactly what the changes need to be instead of discussing what they might be in an issue. CC @VihasMakwana who did the initial port of the Beat ES registry storage to the collector storage interface.
@cmacknz, just as an update after discussions with @andrewkroh, latest commits have removed dual mode operation for the moment and have simplified the receiver operation. |
|
@andrewkroh, @axw, @cmacknz if this PR looks good atm, can we proceed with the approval ? |
|
|
||
| // emitRaw sends events as raw JSON body maps. Each log record body is a map | ||
| // with key "message" containing the raw Akamai JSON string. The downstream | ||
| // pipeline should set elastic.mapping.mode: bodymap via a transform processor |
There was a problem hiding this comment.
Why don't you set the mapping mode attribute here directly instead of requiring it to be set in a transform processor?
There was a problem hiding this comment.
This is mainly to stick to the otel design philosophy. If we want to commit this to upstream, should the receiver explicitly define the mapping mode ? Won't that tightly couple the receiver behaviour to Elastic specific implementations by default ?
There was a problem hiding this comment.
This doesn't follow any upstream convention, the bodymap mapping mode is Elastic specific and we made it for ourselves to allow transporting non-OTel data in the OTLP logs signal.
The JSON document is ECS is it now? This entire mode of execution (along with beats receivers) is vendor specific and that's on purpose. It lets us standardize on the collector as a data collection framework without having to migrate customers outside of Observability to OTLP+SemConv, which has no automatic solution.
There was a problem hiding this comment.
The JSON document in output_mode:raw is the raw json event wrapped in the message field, it does not have any ecs specific mapping as of yet. The entire ecs mapping happens in the ingest pipelines. In otel mode we do the sem-cov mapping in the receiver itself.
Because this JSON is the raw event without any modifications, in this receivers case, I thought it made sense for the mapping to be set using the transform processor. But since bodymap is an Elastic convention, it probably makes sense to tie it into the receiver itself. I will make this change and test it, please let me know if there are any other concerns regarding the current approach and I can address them in the next commit accordingly.
There was a problem hiding this comment.
Yes it's less about ECS then, and more what structure you want the document to have coming out of the exporter. bodymap will send only the JSON event without any of the containing OTLP logs structure, without bodymap you'd send the OTLP log as is with no attributes or scope values set.
People who want the OTLP structure will just use the otel mode. I don't think there's a use for wrapping sending the raw JSON event in an OTLP log record with none of the attributes set properly.
736451f to
9626a9c
Compare
|
@andrewkroh, addressed all your suggestions and removed OTEL mode for now, this means we no longer require an explicit output_format anymore. @cmacknz, implemented your suggestions and coupled I had claude re-write the commit history and clean it up for easy review. The config is much simpler now and hopefully it will be easier to maintain as an initial release. |
9626a9c to
96b36c1
Compare
That might help if someone is starting a fresh review. But for reviewers that already started it means re-reviewing everything rather than doing a differential review of changes since last review. |
axw
left a comment
There was a problem hiding this comment.
LGTM. I've still only skimmed it.
Native Akamai SIEM Receiver
This is a dedicated OTel receiver for the Akamai SIEM API. It implements polling, the chain state machine, and NDJSON streaming directly with native OTel Collector interfaces. It targets the existing data stream and ingest pipeline in Elasticsearch for the Akamai integration.
Why
The CEL-based input works but uses a generic CEL execution engine, which adds overhead and limits control over Akamai-specific offset and chain details. This receiver communicates with the Akamai API directly through EdgeGrid authentication, NDJSON streaming, offset pagination, and chain recovery, without the CEL layer.
Related: elastic/security-integrations#733
Proposed Commit message
How it works
The receiver polls
/siem/v1/configs/{id}using EdgeGrid HMAC-SHA256 signed requests. The responses are gzipped NDJSON. A scanner goroutine reads lines into a bounded channel (stream_buffer_size). It uses a one-line-delay pattern that separates events from the trailing offset-context line. A consumer goroutine batches these events and callsConsumeLogsfor each batch (batch_size). Peak memory is limited tostream_buffer_size + batch_sizeevents, regardless of the page size.The cursor state persists through the OTel storage extension interface (
storage.Client) after each successful page. Storage is optional. Without it, chain state still tracks across poll cycles in memory, but every collector restart will re-fetch frominitial_lookback.The poller operates a three-branch state machine:
Offset TTL detection, 416 recovery, invalid timestamp retries, and "from too old" clamping are all managed within the state machine.
Output shape
Each log record carries the raw Akamai JSON in
LogRecord.Body, with a map keyed bymessage, alongsidedata_stream.{type,dataset,namespace}body keys for Kibana filters. The samedata_stream.*values are also included in resource attributes for the Elasticsearch exporter’s dynamic routing. The scope carrieselastic.mapping.mode: bodymap, allowing the ES exporter to serialize the body map fields directly into the indexed document.Scope attributes are part of the
plog.Logsdata structure and endure batch processor flushes and exporter queues without needingmetadata_keysconfiguration. This is recommended by the ES exporter migration docs. Since the receiver writes the scope attribute itself, there is no need for atransformprocessor in the user pipeline; the common case simplifies to[batch].data_stream.*defaults tologs / akamai.siem / default, which can be configured on the receiver. The Akamai integration’s ingest pipeline handles ECS enrichment; the receiver does not parse or transform event content. Existing Kibana dashboards are immediately usable.Observability
There are 16 metrics covering API health (
requests,request_errors,request_duration,bytes_received), event flow (events_received,events_emitted,events_per_page,events_per_second), pagination (pages_processed,cursor_persists,offset_expired,offset_ttl_drops), recovery (recovery_attempts,invalid_timestamp_retries), and timing (page_processing_time,poll_duration).Spans cover
Poll,FetchPage,ProcessPage,EmitEvents, andPersistCursor. For non-200 responses, theFetchPagespan includesakamai.api.status_code,akamai.api.detail, andakamai.api.body(truncated to 2 KB). This helps operators debug proxy errors and HTML error pages without needing debug logs.Benchmarks
For the mock Akamai API, 100k events per page, nop exporter, using Apple M1 Max, the metrics are:
The process is I/O-bound on gzip decompression and NDJSON streaming. Body-map construction effectively uses zero CPU per event.
What's in the PR
The PR contains 15 commits, organized for review:
feat: add module scaffold and telemetry definitionsgo.mod,Makefile,metadata.yaml(16 metrics), generated metadata code,documentation.md.feat: add EdgeGrid HMAC-SHA256 authenticationhttp.RoundTrippertransport wrapper.feat: add HTTP client and NDJSON streamingAPIErrortypes,StreamEventswith one-line-delay pattern.feat: add cursor persistence via storage extensionCursor,CursorStoreoverstorage.Client, TTL detection.feat: add poller state machine and fetch loopfeat: add config and validationConfig(squashedconfighttp.ClientConfig),data_streamblock,Validate.feat: add factory wiringreceiver.Factory,createLogsReceiver.feat: add receiver lifecycle and bodymap emissionStart/Shutdown,pollLoop,emitEvents(body map + scope + resource attrs).test: add receiver, integration, and tracing teststest: add benchmarksemitEventsand full-poll benchmarks at multiple event counts.docs: add READMEdocs: add architecture diagramsfeat(distributions/elastic-components): register akamaisiemreceiverchore: register akamaisiemreceiver ownership and ignore local artifactsCODEOWNERSentry,.gitignoreadditions.chore: regenerate metadatamdatagenoutput refresh.Package structure
Tests
Integration tests run against a local
httptestserver with realistic NDJSON fixtures. They cover body map shape, scope and resource attribute injection, cursor persistence through a mock storage extension, severity defaults, and error recovery paths. Unit tests address config validation, EdgeGrid signing, NDJSON streaming, cursor operations, and poller state transitions. Benchmarks gauge emit throughput and full poll cycles.End-to-end tests against a local Elasticsearch 8.17 stack with the Akamai integration installed confirm that documents reach
logs-akamai.siem-defaultwith full ECS enrichment from the ingest pipeline. This is true for both batch and non-batch pipeline configurations.Quick start
The minimum required setup includes just the receiver and an exporter. Everything else—
batch,file_storage,sending_queue, metrics endpoint—is optional and noted in the README's scenarios. Additional scenarios, such as file export with rotation, console debug, and high-throughput production with persistent queueing, are detailed in the README.Screenshots
ECS Discover Dashboard:
Akamai SIEM Dashboard 1:
Akamai SIEM Dashboard 2: