Host Monitoring with the OTel Collector | Logfire
The OpenTelemetry Collector’s hostmetrics receiver reads CPU, memory, disk, filesystem, network, paging and process metrics from the machine the Collector is running on and ships them to Logfire: no SDK required, no application changes. Hosts reporting these metrics show up on the Hosts page in Logfire, and the metric series are queryable in Metrics, SQL Workbench, and any dashboard you build on top of them.
This is also the smallest possible working Collector configuration. The same shape works whether the Collector runs as a daemon on a bare VM, a sidecar next to your app, or a DaemonSet in Kubernetes; only the deployment wrapper changes.
receivers:
hostmetrics:
collection_interval: 60s
scrapers:
cpu:
metrics:
system.cpu.utilization:
enabled: true
memory:
metrics:
system.memory.utilization:
enabled: true
load:
disk:
exclude:
devices: ['^(loop|ram)[0-9]+$']
match_type: regexp
filesystem:
include_virtual_filesystems: false
metrics:
system.filesystem.utilization:
enabled: true
network:
exclude:
interfaces: [lo, 'veth.*']
match_type: regexp
paging:
processes:
processors:
resourcedetection:
detectors: [env, system]
system:
hostname_sources: [os]
batch:
exporters:
otlphttp:
endpoint: "https://logfire-eu.pydantic.dev" # or https://logfire-us.pydantic.dev for the US region
headers:
Authorization: "Bearer ${env:LOGFIRE_TOKEN}"
service:
pipelines:
metrics:
receivers: [hostmetrics]
processors: [resourcedetection, batch]
exporters: [otlphttp]
A few things worth calling out:
resourcedetectionadds thehost.name(and on cloud VMs,cloud.provider,cloud.region, etc.) resource attributes to every metric. The Hosts page groups byhost.name, so a Collector that omits this processor won’t appear there.*.utilizationmetrics are off by default in the receiver, but the Hosts page expects them. Enablingsystem.cpu.utilization,system.memory.utilization, andsystem.filesystem.utilizationpopulates the CPU, Memory, and disk columns directly instead of requiring a downstream rate calculation.- Scraper list is selected explicitly. Drop the scrapers you don’t need to reduce metric volume.
processesreports a few aggregate process counts per host. The similarly namedprocessscraper reports metrics for every process ID (PID) and is intentionally not enabled here. - The endpoint must match the region your project lives in (
logfire-euorlogfire-us). The token is a Logfire write token; pass it via theLOGFIRE_TOKENenvironment variable on the Collector workload. - Keep
collection_interval: 60sunless you have a specific need for finer resolution. The standalone receiver already defaults to one minute, but some deployment presets override it to 10 seconds. A 10-second interval sends six times as many datapoints as a 60-second interval, which usually adds cost without making host trends more useful.
Each entry under scrapers: enables one source of host metrics. Pick the ones you need; an empty value (cpu:) accepts the receiver defaults.
| Scraper | What it emits | Linux | macOS | Windows |
|---|---|---|---|---|
cpu | CPU time broken down by state (user, system, idle, iowait, …), aggregated across cores by default. Enabling the cpu metric attribute splits it per core. | yes | yes | yes |
memory | Used / free / cached / buffered bytes and memory utilization. | yes | yes | yes |
load | 1, 5, and 15 minute system load averages. | yes | yes | yes |
disk | Block-device I/O counters: bytes read/written, operations, weighted I/O time. | yes | yes | yes |
filesystem | Mounted-filesystem usage and inode counts, per device and mountpoint. | yes | yes | yes |
network | Per-interface bytes, packets, errors, dropped packets, and TCP connection counts. | yes | yes | yes |
paging | Swap usage and page-in / page-out counters. | yes | yes | yes |
processes | Aggregate counts of running, blocked, and sleeping processes on the host. | yes | yes | no |
process | Per-process CPU, memory, and disk I/O for every running PID. | yes | yes | yes |
For the exhaustive list of metric names and attributes each scraper emits, see the hostmetricsreceiver documentation.
The processes and process scrapers are the two you have to think about.
processesemits a handful of aggregate counts per host. It’s cheap.processemits one set of series per process PID: every short-livedps, every Node worker, everykubectl exec. On Linux, its default metrics produce roughly seven datapoints per PID on every collection, covering CPU states, disk read/write, memory usage, and virtual memory. The resource also includes the PID and can include the executable, command line, and owner. On a busy host or build agent this can create thousands of datapoints per collection, and each new PID is a fresh series even if the binary is identical.
Disk, filesystem, and network metrics also multiply by device, mount point, and interface. The minimal configuration filters loop and memory-backed (ram) devices, virtual filesystems, the loopback interface, and virtual Ethernet interfaces. Adjust those filters to your environment rather than collecting infrastructure you never query.
If you turn on process, scope it. The receiver supports include and exclude filters on process name; we recommend using one of them. It does not provide a “top N processes” limit.
receivers:
hostmetrics:
collection_interval: 60s
scrapers:
process:
mute_process_name_error: true
include:
names: [postgres, redis-server, nginx, node, python]
match_type: strict
Or, equivalently, exclude the noisy ones:
process:
mute_process_name_error: true
exclude:
names: [".*\\.tmp", "sshd", "bash", "sh"]
match_type: regexp
mute_process_name_error: true silences the warnings the receiver logs when it can’t read /proc/<pid>/comm for a process that exited between scrape ticks: common, noisy, and not actionable.
The resourcedetection processor attaches cloud.provider, cloud.region, host.id, and friends to every metric so cloud VMs land on the Hosts page with the right metadata and so dashboards can slice by region. Enable the detectors that match where the Collector might run:
processors:
resourcedetection:
detectors: [env, system, ec2, gcp, azure]
timeout: 5s
override: false
system:
hostname_sources: [os]
A few details:
- Order matters. Detectors run left-to-right, and with
override: falsethe first detector to set an attribute wins. Putenvfirst so anything you’ve baked intoOTEL_RESOURCE_ATTRIBUTES(a deployment name, a cluster tag) takes precedence over auto-detected values. ec2,gcp, andazureeach hit the cloud provider’s instance metadata service. They cost nothing on the wrong cloud (the detector times out and moves on) so it’s safe to list all three on a config that might be deployed anywhere.- For Kubernetes, add the matching managed-cluster detector (
eks,gke, oraks) to pick upk8s.cluster.name. See the resourcedetection processor README for the full list.
In Kubernetes you want one Collector per node, scraping that node’s host metrics. That means a DaemonSet, and a few specific bits of pod spec so the receiver reads the node’s CPU, memory, network, and process tables instead of the container’s namespaced view.
Three things have to be right:
hostNetwork: trueandhostPID: trueon the pod, so the network and process scrapers see the node.- Mount the host root at
/host, with the host’s/procand/sysavailable below it (read-only is fine). - Set
root_path: /hoston thehostmetricsreceiver so it reads from those mounts instead of the container root.
receivers:
hostmetrics:
collection_interval: 60s
root_path: /host
scrapers:
cpu:
memory:
load:
disk:
exclude:
devices: ['^(loop|ram)[0-9]+$']
match_type: regexp
filesystem:
include_virtual_filesystems: false
network:
exclude:
interfaces: [lo, 'veth.*']
match_type: regexp
paging:
processes:
processors:
resourcedetection:
detectors: [env, system, ec2, gcp, azure]
timeout: 5s
override: false
batch:
exporters:
otlphttp:
endpoint: "https://logfire-eu.pydantic.dev" # or https://logfire-us.pydantic.dev for the US region
headers:
Authorization: "Bearer ${env:LOGFIRE_TOKEN}"
service:
pipelines:
metrics:
receivers: [hostmetrics]
processors: [resourcedetection, batch]
exporters: [otlphttp]
And the DaemonSet that runs it:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: otel-collector-hostmetrics
namespace: observability
spec:
selector:
matchLabels:
app: otel-collector-hostmetrics
template:
metadata:
labels:
app: otel-collector-hostmetrics
spec:
hostNetwork: true
hostPID: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:latest
args: ["--config=/etc/otel/config.yaml"]
env:
- name: LOGFIRE_TOKEN
valueFrom:
secretKeyRef:
name: logfire
key: write-token
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: config
mountPath: /etc/otel
- name: hostfs-proc
mountPath: /host/proc
readOnly: true
- name: hostfs-sys
mountPath: /host/sys
readOnly: true
- name: hostfs-root
mountPath: /host
readOnly: true
mountPropagation: HostToContainer
volumes:
- name: config
configMap:
name: otel-collector-hostmetrics
- name: hostfs-proc
hostPath:
path: /proc
- name: hostfs-sys
hostPath:
path: /sys
- name: hostfs-root
hostPath:
path: /
A couple of things to be deliberate about:
hostNetwork: trueputs the Collector on the node’s network namespace, which is what makes thenetworkscraper return real interface stats and letsresourcedetection’s cloud detectors reach the instance metadata service. It also means the Collector’s ports are exposed on the node, so don’t bind anything you don’t intend to.hostPID: trueis what lets theprocesses/processscrapers see PIDs other than the Collector’s own.- The
filesystemscraper needs the host root mounted at the receiver’sroot_path(here/host, withmountPropagation: HostToContainer) to report node disk usage rather than the container’s overlay. If you only care about CPU/memory/network, you can omit that mount and dropfilesystemfrom the scraper list.
Outside of Kubernetes, no special configuration is required: the Collector already has the host’s /proc and /sys. Drop the config in place and run:
LOGFIRE_TOKEN=<your-write-token> \
otelcol-contrib --config otel-collector-config.yaml
For long-running deployments, wrap that in a systemd unit (or your init system of choice) and put LOGFIRE_TOKEN in an EnvironmentFile= it reads at startup rather than baking the token into the unit.
Within a minute or two of the Collector starting, the host shows up on the Hosts page keyed by host.name. From there:
- Click the host to drill into per-host CPU, memory, disk, and network charts.
- Open the SQL Workbench to query the raw metric series: useful for ad-hoc questions like “show me every host where filesystem utilization is above 90%”.
- Build a dashboard on top of the metrics if you want a persistent view.
If a host doesn’t appear, the cause is almost always one of: missing resourcedetection (no host.name), wrong region in the otlphttp endpoint, or (in Kubernetes) a missing root_path: /host or one of the hostPath mounts, so the receiver is happily scraping the container’s view instead of the node’s.