prometheus/node_exporter · error

failed to open textfile data file

Error message

failed to open textfile data file %q: %w

What it means

textFileCollector.processFile opens each file in the textfile directory with os.Open; any open failure (missing file, permission denied, path is a directory) is wrapped as this error. node_exporter's textfile collector reads Prometheus exposition-format files dropped by other processes and merges them into the scrape.

Solutions

  1. Check file permissions: ensure the user node_exporter runs as can read the files (chmod 644, correct group)
  2. Make writers atomic: write to a temp file and rename() into place instead of deleting/recreating during scrape
  3. Ensure the textfile directory (--collector.textfile.directory) contains only regular readable files, not subdirectories
  4. If the wrapped cause is permission denied, adjust SELinux/AppArmor or directory ownership

Example fix

// before (writer): non-atomic delete during scrape
rm /var/lib/node_exporter/textfile/foo.prom && write foo.prom
// after (writer): atomic replace
tmp=$(mktemp /var/lib/node_exporter/textfile/foo.prom.XXXXXX)
write "$tmp" && chmod 644 "$tmp" && mv "$tmp" /var/lib/node_exporter/textfile/foo.prom
Defensive patterns

Strategy: validation

Validate before calling

files, _ := os.ReadDir(textfileDir)
for _, f := range files {
    if !f.Type().IsRegular() { continue }
    if fh, err := os.Open(filepath.Join(textfileDir, f.Name())); err != nil {
        log.Warn("unreadable textfile", "name", f.Name(), "err", err)
    } else { fh.Close() }
}

Try / catch

if err := coll.Update(ch); err != nil {
    log.Warn("textfile scrape issue", "err", err) // surfaced via node_exporter textfile scrape error metric
}

Prevention

When it happens

Trigger: A file listed/visible in the textfile directory disappears between readdir and open (writer removing files); the file is a directory; the exporter user lacks read permission; stale symlink targets.

Common situations: Cron jobs that `mv` or delete .prom files while a scrape is in flight; files written with root-only permissions while node_exporter runs as the prometheus user; misconfigured directory containing subdirectories; broken symlinks left by deploy scripts.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of prometheus/node_exporter@17ddd77c59 (2026-09-07). Data as JSON: /api/errors/2973f75250e3c0b4. Report an issue: GitHub.

Appendix: source

Thrown at collector/textfile.go:292

	ch <- prometheus.MustNewConstMetric(
		prometheus.NewDesc(
			"node_textfile_scrape_error",
			"1 if there was an error opening or reading a file, 0 otherwise",
			nil, nil,
		),
		prometheus.GaugeValue, errVal,
	)

	return nil
}

// processFile processes a single file, returning its modification time on success.
func (c *textFileCollector) processFile(dir, name string) (*time.Time, map[string]*dto.MetricFamily, error) {
	path := filepath.Join(dir, name)
	f, err := os.Open(path)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to open textfile data file %q: %w", path, err)
	}
	defer f.Close()

	parser := expfmt.NewTextParser(model.UTF8Validation)
	families, err := parser.TextToMetricFamilies(f)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse textfile data from %q: %w", path, err)
	}

	if hasTimestamps(families) {
		return nil, nil, fmt.Errorf("textfile %q contains unsupported client-side timestamps, skipping entire file", path)
	}

	// Only stat the file once it has been parsed and validated, so that
	// a failure does not appear fresh.
	stat, err := f.Stat()
	if err != nil {
		return nil, families, fmt.Errorf("failed to stat %q: %w", path, err)

View on GitHub (pinned to 17ddd77c59)