> ## Documentation Index
> Fetch the complete documentation index at: https://docs.futurex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Deploy the OpenTelemetry Collector

> Complete OpenTelemetry Collector configuration that receives Futurex HSM syslog over UDP, parses it into fields, and exports it to Observe.

The collector receives HSM syslog on a UDP port, parses each message into fields, and exports the result to Observe over OTLP.

Deploy the collector before you enable forwarding on the HSM. UDP is best effort, so any message the HSM sends before the listener exists is lost.

## Use the udplog receiver, not the syslog receiver

The HSM emits a non-standard RFC 3164 variant with no hostname field and no tag field. The collector's `syslog` receiver accepts these messages **without reporting an error**, but it discards the useful structure: it produces no hostname and collapses the entire event into one string.

This guide therefore takes the raw datagram with the `udplog` receiver and applies an explicit regular expression. For the message anatomy and a side-by-side comparison of both approaches, see [Appendix A: Futurex syslog message format](./Appendix_A_Futurex_syslog_message_format).

<Warning>
  If you configure the `syslog` receiver with `protocol: rfc3164`, the integration appears to work. Records reach Observe and no errors appear in the collector log. Only when you try to filter or chart the data do you discover that device, service, level, and event are all trapped inside a single unparsed string.
</Warning>

## Configuration

Save the following as `otel-collector.yaml`. Change `listen_address` if you chose a different port.

```yaml wrap theme={null}
receivers:
  udplog/hsm:
    listen_address: 0.0.0.0:5515
    operators:
      # Field names follow the appliance's own web portal, which documents the
      # syslog row as: Timestamp | Service | Log Level | Message.
      - type: regex_parser
        id: fx_envelope
        regex: '^<(?P<pri>\d{1,3})>\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s*:\s*\|(?P<fx_time>\d{4}:\d{2}:\d{2}:\d{2}:\d{2}:\d{2}\.\d+)\s+(?P<device>\S+)\s+(?P<service>\S+)\s+(?P<level>\S+)\s+(?P<message>.*?)\s*\|\s*$'
        parse_from: body
        parse_to: attributes
        on_error: send
        timestamp:
          # The appliance stamps in UTC and the syslog header carries no zone,
          # so use the microsecond payload timestamp and pin the location.
          parse_from: attributes.fx_time
          layout_type: gotime
          layout: '2006:01:02:15:04:05.000000'
          location: UTC
        severity:
          parse_from: attributes.level
          mapping:
            debug: debug
            info:
              - info
              - notice
            warn:
              - warning
              - warn
            error:
              - error
              - err
            fatal:
              - critical
              - crit
              - alert
              - emerg

      # Namespace the parsed fields so they cannot collide with OpenTelemetry
      # semantic conventions. "service" in particular would clash with
      # service.name.
      - type: move
        from: attributes.device
        to: attributes["hsm.device"]
        on_error: send
      - type: move
        from: attributes.service
        to: attributes["hsm.service"]
        on_error: send
      - type: move
        from: attributes.level
        to: attributes["hsm.level"]
        on_error: send
      - type: move
        from: attributes.message
        to: attributes["hsm.message"]
        on_error: send

      # The syslog priority is kern/notice on every record the appliance emits,
      # so it carries no routing value. Drop the scratch fields. The unmodified
      # datagram remains in the log body.
      - type: remove
        field: attributes.pri
        on_error: send
      - type: remove
        field: attributes.fx_time
        on_error: send

      # Classify the high-value security events so dashboards and alerts filter
      # on one stable field instead of matching substrings.
      - type: add
        field: attributes["hsm.event_type"]
        value: other
      - type: add
        field: attributes["hsm.event_type"]
        value: auth_failure
        if: 'attributes["hsm.message"] != nil and attributes["hsm.message"] matches "Failed Log In Attempt"'
      - type: add
        field: attributes["hsm.event_type"]
        value: auth_success
        if: 'attributes["hsm.message"] != nil and attributes["hsm.message"] matches "Log In User"'
      - type: add
        field: attributes["hsm.event_type"]
        value: config_change
        if: 'attributes["hsm.message"] != nil and attributes["hsm.message"] matches "^CONFIG:" and attributes["hsm.message"] matches "Settings|Forwarding"'
      - type: add
        field: attributes["hsm.event_type"]
        value: identity_update
        if: 'attributes["hsm.message"] != nil and attributes["hsm.message"] matches "fxhsm::auth::Identity"'
      # Recurring clock synchronization from a client application. Classified so
      # that it does not accumulate under "other".
      - type: add
        field: attributes["hsm.event_type"]
        value: time_change
        if: 'attributes["hsm.message"] != nil and attributes["hsm.message"] matches "Time Changed"'
      # Brute-force lockout. Listed last so that it wins over auth_failure,
      # whose pattern is a substring of this message.
      - type: add
        field: attributes["hsm.event_type"]
        value: auth_lockout
        if: 'attributes["hsm.message"] != nil and attributes["hsm.message"] matches "Too Many Failed Log In Attempts"'

processors:
  memory_limiter:
    check_interval: 5s
    limit_mib: 256
    spike_limit_mib: 64

  resource:
    attributes:
      - key: service.name
        value: futurex-hsm
        action: upsert
      - key: vendor
        value: futurex
        action: upsert
      - key: deployment.environment
        value: production
        action: upsert

  # Promote the appliance identifier out of the log record so events from
  # several HSMs can be separated in Observe.
  transform/host:
    error_mode: ignore
    log_statements:
      - context: log
        statements:
          - set(resource.attributes["host.name"], attributes["hsm.device"]) where attributes["hsm.device"] != nil

  batch:
    timeout: 5s
    send_batch_size: 512

exporters:
  # Observe supports OTLP over HTTP only. gRPC is not available, so the
  # exporter must be otlphttp and never otlp.
  # The exporter appends /v1/logs to this endpoint.
  otlphttp/observe:
    endpoint: https://${env:OBSERVE_CUSTOMER}.collect.observeinc.com/v2/otel
    headers:
      Authorization: Bearer ${env:OBSERVE_TOKEN}
    compression: gzip
    sending_queue:
      enabled: true
      queue_size: 1000
    retry_on_failure:
      enabled: true
      initial_interval: 5s
      max_interval: 30s
      max_elapsed_time: 300s

  # Remove the debug exporter once the integration is validated.
  debug:
    verbosity: detailed

service:
  telemetry:
    logs:
      level: info
  pipelines:
    logs:
      receivers: [udplog/hsm]
      processors: [memory_limiter, resource, transform/host, batch]
      exporters: [otlphttp/observe, debug]
```

<Note>
  Collector versions from `v0.159.0` onward log a deprecation warning for the `otlphttp` exporter name and prefer `otlp_http`. The `otlphttp` name still works and remains compatible with older collectors, so this guide keeps it. If you standardize on a recent collector, rename it.
</Note>

The `memory_limiter` processor must be listed first in the pipeline so that it can reject data before other processors allocate memory.

## Run the collector

Both methods below read the token from the environment so it never appears in the configuration file.

### Docker

```bash wrap theme={null}
docker run -d --name otelcol-observe \
  -p 5515:5515/udp \
  -e OBSERVE_CUSTOMER=123456789012 \
  --env-file /etc/observe/token.env \
  -v /etc/observe/otel-collector.yaml:/etc/otelcol/config.yaml:ro \
  otel/opentelemetry-collector-contrib:0.159.0 \
  --config=/etc/otelcol/config.yaml
```

Pin the image tag rather than using `latest`, so a future release cannot change parsing behavior without your knowledge.

### systemd

Install the Contrib binary, then create `/etc/systemd/system/otelcol-observe.service`:

```ini theme={null}
[Unit]
Description=OpenTelemetry Collector for Futurex HSM logs
After=network-online.target

[Service]
Environment=OBSERVE_CUSTOMER=123456789012
EnvironmentFile=/etc/observe/token.env
ExecStart=/usr/local/bin/otelcol-contrib --config=/etc/observe/otel-collector.yaml
Restart=on-failure
User=otelcol

[Install]
WantedBy=multi-user.target
```

Enable and start it:

```bash theme={null}
sudo systemctl enable --now otelcol-observe
```

## Confirm the collector started

```bash theme={null}
docker logs otelcol-observe 2>&1 | grep 'Everything is ready'
```

The receiver logs a line confirming the stanza receiver started. If the collector exits immediately, the configuration failed to load. Check for indentation errors and confirm you are running the Contrib distribution, because the core distribution has no `udplog` receiver.

## Restrict access to the listener

The syslog listener accepts unauthenticated UDP from any host that can reach it. Restrict it to the HSM addresses at the host firewall. For example, with `firewalld`:

```bash wrap theme={null}
sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.0.0.10/32 port port=5515 protocol=udp accept'
sudo firewall-cmd --reload
```

Replace `10.0.0.10` with the address of each HSM that forwards logs.


## Related topics

- [Log ingestion with Observe](/Integrations/VirtuCrypt/Log_ingestion_with_Observe/Log_ingestion_with_Observe.md)
- [Appendix B: Troubleshooting](/Integrations/VirtuCrypt/Log_ingestion_with_Observe/Appendix_B_Troubleshooting.md)
- [Before you start](/Integrations/VirtuCrypt/Log_ingestion_with_Observe/Before_you_start.md)
- [Deploy the service](/Integrations/CryptoHub/Data_protection/OpenSSL_Engine/Deploy_the_service.md)
- [Create an Observe datastream and token](/Integrations/VirtuCrypt/Log_ingestion_with_Observe/Create_an_Observe_datastream_and_token.md)
