Build production-grade custom Prometheus exporters in Python and Go to collect application-specific metrics. Learn exporter architecture, metric types, systemd deployment, and Prometheus integration for comprehensive application monitoring.
Prerequisites
- Root or sudo access
- Python 3.8+ installed
- Go 1.19+ installed
- Prometheus server running
- Basic understanding of systemd services
What this solves
Custom Prometheus exporters allow you to collect application-specific metrics that aren't available through standard exporters. This tutorial shows you how to build HTTP-based exporters in Python and Go, implement process and file-based metric collection, and deploy them as systemd services with proper Prometheus integration.
Understanding Prometheus exporter architecture
Prometheus exporters follow a pull-based model where Prometheus scrapes metrics from HTTP endpoints. Exporters expose metrics in a specific text format at /metrics endpoints, typically running on dedicated ports.
Metric types and formats
Prometheus supports four core metric types that serve different monitoring purposes:
| Type | Purpose | Example Use Case |
|---|---|---|
| Counter | Monotonically increasing values | HTTP requests, errors, processed jobs |
| Gauge | Values that can go up or down | Memory usage, active connections, queue size |
| Histogram | Distribution of values with buckets | Request duration, response sizes |
| Summary | Similar to histogram with quantiles | Response times with percentiles |
Step-by-step implementation
Install required dependencies
Install Python development tools and Go compiler for building custom exporters.
sudo apt update
sudo apt install -y python3 python3-pip python3-venv golang-go curl
sudo dnf install -y python3 python3-pip golang curl
sudo dnf groupinstall -y "Development Tools"
Create dedicated user for exporters
Create a non-privileged user to run the exporters for security isolation.
sudo useradd --system --no-create-home --shell /bin/false prometheus-exporters
sudo mkdir -p /opt/prometheus-exporters
sudo chown prometheus-exporters:prometheus-exporters /opt/prometheus-exporters
Build Python-based HTTP exporter
Create a Python exporter that collects application metrics and exposes them via HTTP.
sudo mkdir -p /opt/prometheus-exporters/python-app
cd /opt/prometheus-exporters/python-app
python3 -m venv venv
source venv/bin/activate
pip install prometheus_client psutil requests
Create Python exporter application
Build a comprehensive exporter that monitors application processes, file metrics, and custom business logic.
#!/usr/bin/env python3
import time
import psutil
import os
import json
from prometheus_client import start_http_server, Gauge, Counter, Histogram, Info
from prometheus_client.core import CollectorRegistry
from threading import Thread
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class ApplicationExporter:
def __init__(self, config_file='/etc/prometheus-exporters/app-config.json'):
self.config = self.load_config(config_file)
self.registry = CollectorRegistry()
# Define metrics
self.app_info = Info('app_version', 'Application version info', registry=self.registry)
self.process_count = Gauge('app_processes_total', 'Number of application processes', ['name'], registry=self.registry)
self.file_size_bytes = Gauge('app_file_size_bytes', 'Size of monitored files', ['path'], registry=self.registry)
self.file_age_seconds = Gauge('app_file_age_seconds', 'Age of monitored files', ['path'], registry=self.registry)
self.request_count = Counter('app_requests_total', 'Total application requests', ['method', 'endpoint'], registry=self.registry)
self.response_time = Histogram('app_response_duration_seconds', 'Response time histogram', ['endpoint'], registry=self.registry)
self.memory_usage_bytes = Gauge('app_memory_usage_bytes', 'Memory usage by process', ['process'], registry=self.registry)
self.cpu_usage_percent = Gauge('app_cpu_usage_percent', 'CPU usage by process', ['process'], registry=self.registry)
# Set application info
self.app_info.info({
'version': self.config.get('version', '1.0.0'),
'environment': self.config.get('environment', 'production')
})
def load_config(self, config_file):
try:
with open(config_file, 'r') as f:
return json.load(f)
except FileNotFoundError:
logger.warning(f"Config file {config_file} not found, using defaults")
return {
'monitored_processes': ['nginx', 'postgres', 'redis-server'],
'monitored_files': ['/var/log/app.log', '/var/lib/app/data.db'],
'version': '1.0.0',
'environment': 'production'
}
def collect_process_metrics(self):
"""Collect metrics for monitored processes"""
for process_name in self.config.get('monitored_processes', []):
count = 0
total_memory = 0
total_cpu = 0
for proc in psutil.process_iter(['pid', 'name', 'memory_info', 'cpu_percent']):
try:
if proc.info['name'] == process_name:
count += 1
total_memory += proc.info['memory_info'].rss
total_cpu += proc.info['cpu_percent']
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
self.process_count.labels(name=process_name).set(count)
if count > 0:
self.memory_usage_bytes.labels(process=process_name).set(total_memory)
self.cpu_usage_percent.labels(process=process_name).set(total_cpu)
def collect_file_metrics(self):
"""Collect metrics for monitored files"""
for file_path in self.config.get('monitored_files', []):
try:
stat = os.stat(file_path)
self.file_size_bytes.labels(path=file_path).set(stat.st_size)
self.file_age_seconds.labels(path=file_path).set(time.time() - stat.st_mtime)
except FileNotFoundError:
logger.warning(f"File {file_path} not found")
self.file_size_bytes.labels(path=file_path).set(0)
self.file_age_seconds.labels(path=file_path).set(-1)
def simulate_request_metrics(self):
"""Simulate request metrics (replace with actual application integration)"""
endpoints = ['/api/users', '/api/orders', '/api/health']
methods = ['GET', 'POST', 'PUT']
import random
endpoint = random.choice(endpoints)
method = random.choice(methods)
response_time = random.uniform(0.01, 2.0)
self.request_count.labels(method=method, endpoint=endpoint).inc()
self.response_time.labels(endpoint=endpoint).observe(response_time)
def collect_metrics(self):
"""Main metric collection method"""
while True:
try:
self.collect_process_metrics()
self.collect_file_metrics()
self.simulate_request_metrics()
time.sleep(10) # Collect metrics every 10 seconds
except Exception as e:
logger.error(f"Error collecting metrics: {e}")
time.sleep(10)
def start(self, port=8000):
"""Start the HTTP server and metric collection"""
logger.info(f"Starting application exporter on port {port}")
start_http_server(port, registry=self.registry)
# Start metric collection in background thread
collection_thread = Thread(target=self.collect_metrics)
collection_thread.daemon = True
collection_thread.start()
logger.info("Application exporter started successfully")
return collection_thread
if __name__ == '__main__':
exporter = ApplicationExporter()
collection_thread = exporter.start(port=8000)
try:
# Keep the main thread alive
while True:
time.sleep(1)
except KeyboardInterrupt:
logger.info("Shutting down application exporter")
Create exporter configuration
Configure which processes and files to monitor for metrics collection.
sudo mkdir -p /etc/prometheus-exporters
{
"version": "2.1.0",
"environment": "production",
"monitored_processes": [
"nginx",
"postgres",
"redis-server",
"gunicorn",
"celery"
],
"monitored_files": [
"/var/log/nginx/access.log",
"/var/log/nginx/error.log",
"/var/lib/postgresql/data/postgresql.log",
"/var/log/app/application.log",
"/etc/nginx/nginx.conf"
]
}
Build Go-based exporter
Create a high-performance Go exporter for collecting system and application metrics.
mkdir -p /opt/prometheus-exporters/go-system
cd /opt/prometheus-exporters/go-system
go mod init system-exporter
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp
Create Go exporter application
Implement a Go exporter that provides efficient system metric collection.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Config struct {
Port int `json:"port"`
MetricsPath string `json:"metrics_path"`
ScrapeInterval int `json:"scrape_interval"`
MonitoredPorts []int `json:"monitored_ports"`
LogFiles []string `json:"log_files"`
}
type SystemExporter struct {
config *Config
systemLoad1 prometheus.Gauge
systemLoad5 prometheus.Gauge
systemLoad15 prometheus.Gauge
memoryTotal prometheus.Gauge
memoryAvailable prometheus.Gauge
memoryUsed prometheus.Gauge
diskUsedBytes *prometheus.GaugeVec
diskTotalBytes *prometheus.GaugeVec
networkReceiveBytes *prometheus.CounterVec
networkTransmitBytes *prometheus.CounterVec
portStatus *prometheus.GaugeVec
logFileSizeBytes *prometheus.GaugeVec
logFileModTime *prometheus.GaugeVec
uptime prometheus.Gauge
}
func NewSystemExporter(configPath string) (*SystemExporter, error) {
config, err := loadConfig(configPath)
if err != nil {
return nil, fmt.Errorf("failed to load config: %w", err)
}
exporter := &SystemExporter{
config: config,
systemLoad1: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_load_1m",
Help: "System load average over 1 minute",
}),
systemLoad5: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_load_5m",
Help: "System load average over 5 minutes",
}),
systemLoad15: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_load_15m",
Help: "System load average over 15 minutes",
}),
memoryTotal: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_memory_total_bytes",
Help: "Total system memory in bytes",
}),
memoryAvailable: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_memory_available_bytes",
Help: "Available system memory in bytes",
}),
memoryUsed: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_memory_used_bytes",
Help: "Used system memory in bytes",
}),
diskUsedBytes: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_disk_used_bytes",
Help: "Used disk space in bytes",
},
[]string{"device", "mountpoint"},
),
diskTotalBytes: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_disk_total_bytes",
Help: "Total disk space in bytes",
},
[]string{"device", "mountpoint"},
),
networkReceiveBytes: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "system_network_receive_bytes_total",
Help: "Total bytes received on network interfaces",
},
[]string{"interface"},
),
networkTransmitBytes: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "system_network_transmit_bytes_total",
Help: "Total bytes transmitted on network interfaces",
},
[]string{"interface"},
),
portStatus: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_port_status",
Help: "Status of monitored ports (1=listening, 0=not listening)",
},
[]string{"port"},
),
logFileSizeBytes: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_log_file_size_bytes",
Help: "Size of log files in bytes",
},
[]string{"file"},
),
logFileModTime: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "system_log_file_modification_time",
Help: "Last modification time of log files",
},
[]string{"file"},
),
uptime: prometheus.NewGauge(prometheus.GaugeOpts{
Name: "system_uptime_seconds",
Help: "System uptime in seconds",
}),
}
// Register metrics
prometheus.MustRegister(
exporter.systemLoad1,
exporter.systemLoad5,
exporter.systemLoad15,
exporter.memoryTotal,
exporter.memoryAvailable,
exporter.memoryUsed,
exporter.diskUsedBytes,
exporter.diskTotalBytes,
exporter.networkReceiveBytes,
exporter.networkTransmitBytes,
exporter.portStatus,
exporter.logFileSizeBytes,
exporter.logFileModTime,
exporter.uptime,
)
return exporter, nil
}
func loadConfig(configPath string) (*Config, error) {
config := &Config{
Port: 8001,
MetricsPath: "/metrics",
ScrapeInterval: 15,
MonitoredPorts: []int{22, 80, 443, 5432, 6379},
LogFiles: []string{"/var/log/syslog", "/var/log/auth.log"},
}
if _, err := os.Stat(configPath); os.IsNotExist(err) {
log.Printf("Config file %s not found, using defaults", configPath)
return config, nil
}
data, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, config)
if err != nil {
return nil, err
}
return config, nil
}
func (e *SystemExporter) collectLoadAverage() {
data, err := ioutil.ReadFile("/proc/loadavg")
if err != nil {
log.Printf("Error reading load average: %v", err)
return
}
fields := strings.Fields(string(data))
if len(fields) >= 3 {
if load1, err := strconv.ParseFloat(fields[0], 64); err == nil {
e.systemLoad1.Set(load1)
}
if load5, err := strconv.ParseFloat(fields[1], 64); err == nil {
e.systemLoad5.Set(load5)
}
if load15, err := strconv.ParseFloat(fields[2], 64); err == nil {
e.systemLoad15.Set(load15)
}
}
}
func (e *SystemExporter) collectMemoryStats() {
data, err := ioutil.ReadFile("/proc/meminfo")
if err != nil {
log.Printf("Error reading memory info: %v", err)
return
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
value, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
continue
}
// Convert KB to bytes
value *= 1024
switch fields[0] {
case "MemTotal:":
e.memoryTotal.Set(value)
case "MemAvailable:":
e.memoryAvailable.Set(value)
}
}
// Calculate used memory
total := e.memoryTotal
available := e.memoryAvailable
if total != nil && available != nil {
// This is a simplified calculation
// In production, you'd want more accurate memory calculations
}
}
func (e *SystemExporter) collectPortStatus() {
for _, port := range e.config.MonitoredPorts {
address := fmt.Sprintf("127.0.0.1:%d", port)
conn, err := net.DialTimeout("tcp", address, 1*time.Second)
if err != nil {
e.portStatus.WithLabelValues(strconv.Itoa(port)).Set(0)
} else {
conn.Close()
e.portStatus.WithLabelValues(strconv.Itoa(port)).Set(1)
}
}
}
func (e *SystemExporter) collectLogFileStats() {
for _, logFile := range e.config.LogFiles {
stat, err := os.Stat(logFile)
if err != nil {
log.Printf("Error getting stats for %s: %v", logFile, err)
e.logFileSizeBytes.WithLabelValues(logFile).Set(0)
e.logFileModTime.WithLabelValues(logFile).Set(0)
continue
}
e.logFileSizeBytes.WithLabelValues(logFile).Set(float64(stat.Size()))
e.logFileModTime.WithLabelValues(logFile).Set(float64(stat.ModTime().Unix()))
}
}
func (e *SystemExporter) collectUptime() {
data, err := ioutil.ReadFile("/proc/uptime")
if err != nil {
log.Printf("Error reading uptime: %v", err)
return
}
fields := strings.Fields(string(data))
if len(fields) >= 1 {
if uptime, err := strconv.ParseFloat(fields[0], 64); err == nil {
e.uptime.Set(uptime)
}
}
}
func (e *SystemExporter) collectMetrics() {
for {
e.collectLoadAverage()
e.collectMemoryStats()
e.collectPortStatus()
e.collectLogFileStats()
e.collectUptime()
time.Sleep(time.Duration(e.config.ScrapeInterval) * time.Second)
}
}
func (e *SystemExporter) Start() {
go e.collectMetrics()
http.Handle(e.config.MetricsPath, promhttp.Handler())
log.Printf("System exporter starting on port %d", e.config.Port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", e.config.Port), nil))
}
func main() {
exporter, err := NewSystemExporter("/etc/prometheus-exporters/system-config.json")
if errAutomated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Prometheus Custom Exporters Installation Script
# Installs Python and Go-based Prometheus exporters with systemd services
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Variables
EXPORTER_USER="prometheus-exporters"
INSTALL_DIR="/opt/prometheus-exporters"
CONFIG_DIR="/etc/prometheus-exporters"
PYTHON_PORT=${1:-9090}
GO_PORT=${2:-9091}
usage() {
echo "Usage: $0 [python_port] [go_port]"
echo " python_port: Port for Python exporter (default: 9090)"
echo " go_port: Port for Go exporter (default: 9091)"
exit 1
}
if [[ $# -gt 2 ]]; then
usage
fi
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Cleanup function
cleanup() {
echo -e "${RED}Installation failed. Cleaning up...${NC}"
systemctl stop prometheus-python-exporter 2>/dev/null || true
systemctl stop prometheus-go-exporter 2>/dev/null || true
rm -rf "$INSTALL_DIR" "$CONFIG_DIR"
userdel "$EXPORTER_USER" 2>/dev/null || true
}
trap cleanup ERR
# Detect OS
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
PYTHON_PKG="python3 python3-pip python3-venv"
GO_PKG="golang-go"
DEV_TOOLS="build-essential"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_INSTALL="dnf install -y"
PYTHON_PKG="python3 python3-pip"
GO_PKG="golang"
DEV_TOOLS="gcc gcc-c++ make"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_INSTALL="yum install -y"
PYTHON_PKG="python3 python3-pip"
GO_PKG="golang"
DEV_TOOLS="gcc gcc-c++ make"
;;
*)
echo -e "${RED}Unsupported distro: $ID${NC}"
exit 1
;;
esac
fi
echo -e "${GREEN}[1/8] Updating package cache...${NC}"
$PKG_UPDATE
echo -e "${GREEN}[2/8] Installing dependencies...${NC}"
$PKG_INSTALL $PYTHON_PKG $GO_PKG $DEV_TOOLS curl
echo -e "${GREEN}[3/8] Creating system user and directories...${NC}"
useradd --system --no-create-home --shell /bin/false "$EXPORTER_USER" || true
mkdir -p "$INSTALL_DIR" "$CONFIG_DIR"
chown "$EXPORTER_USER:$EXPORTER_USER" "$INSTALL_DIR" "$CONFIG_DIR"
echo -e "${GREEN}[4/8] Setting up Python exporter...${NC}"
mkdir -p "$INSTALL_DIR/python-app"
cd "$INSTALL_DIR/python-app"
python3 -m venv venv
source venv/bin/activate
pip install prometheus_client psutil requests
cat > app_exporter.py << 'EOF'
#!/usr/bin/env python3
import time
import psutil
import os
import json
from prometheus_client import start_http_server, Gauge, Counter, Histogram
from prometheus_client.core import CollectorRegistry
import logging
import sys
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ApplicationExporter:
def __init__(self, port=9090):
self.port = port
self.registry = CollectorRegistry()
# Define metrics
self.process_count = Gauge('app_processes_total', 'Number of processes', ['name'], registry=self.registry)
self.memory_usage = Gauge('app_memory_usage_bytes', 'Memory usage', ['process'], registry=self.registry)
self.cpu_usage = Gauge('app_cpu_usage_percent', 'CPU usage', ['process'], registry=self.registry)
self.disk_usage = Gauge('app_disk_usage_percent', 'Disk usage', ['mount'], registry=self.registry)
def collect_metrics(self):
# Process metrics
processes = {}
for proc in psutil.process_iter(['name', 'memory_info', 'cpu_percent']):
try:
name = proc.info['name']
processes[name] = processes.get(name, 0) + 1
self.memory_usage.labels(process=name).set(proc.info['memory_info'].rss)
self.cpu_usage.labels(process=name).set(proc.info['cpu_percent'])
except:
continue
for name, count in processes.items():
self.process_count.labels(name=name).set(count)
# Disk usage
for disk in psutil.disk_partitions():
try:
usage = psutil.disk_usage(disk.mountpoint)
self.disk_usage.labels(mount=disk.mountpoint).set(usage.percent)
except:
continue
def run(self):
start_http_server(self.port, registry=self.registry)
logger.info(f"Python exporter started on port {self.port}")
while True:
self.collect_metrics()
time.sleep(15)
if __name__ == '__main__':
port = int(sys.argv[1]) if len(sys.argv) > 1 else 9090
exporter = ApplicationExporter(port)
exporter.run()
EOF
chmod 755 app_exporter.py
chown -R "$EXPORTER_USER:$EXPORTER_USER" "$INSTALL_DIR/python-app"
echo -e "${GREEN}[5/8] Setting up Go exporter...${NC}"
mkdir -p "$INSTALL_DIR/go-app"
cd "$INSTALL_DIR/go-app"
cat > main.go << 'EOF'
package main
import (
"fmt"
"log"
"net/http"
"os"
"strconv"
"time"
"runtime"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
goroutines = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_goroutines_total",
Help: "Number of goroutines",
},
[]string{"type"},
)
memStats = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_memory_stats_bytes",
Help: "Memory statistics",
},
[]string{"type"},
)
)
func init() {
prometheus.MustRegister(goroutines)
prometheus.MustRegister(memStats)
}
func collectMetrics() {
goroutines.With(prometheus.Labels{"type": "runtime"}).Set(float64(runtime.NumGoroutine()))
var m runtime.MemStats
runtime.ReadMemStats(&m)
memStats.With(prometheus.Labels{"type": "alloc"}).Set(float64(m.Alloc))
memStats.With(prometheus.Labels{"type": "sys"}).Set(float64(m.Sys))
memStats.With(prometheus.Labels{"type": "heap"}).Set(float64(m.HeapAlloc))
}
func main() {
port := "9091"
if len(os.Args) > 1 {
port = os.Args[1]
}
go func() {
for {
collectMetrics()
time.Sleep(15 * time.Second)
}
}()
http.Handle("/metrics", promhttp.Handler())
log.Printf("Go exporter started on port %s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
EOF
go mod init prometheus-go-exporter
go mod tidy
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp
go build -o prometheus-go-exporter main.go
chown -R "$EXPORTER_USER:$EXPORTER_USER" "$INSTALL_DIR/go-app"
echo -e "${GREEN}[6/8] Creating configuration files...${NC}"
cat > "$CONFIG_DIR/app-config.json" << EOF
{
"version": "1.0.0",
"environment": "production",
"monitored_processes": ["nginx", "apache2", "httpd"],
"monitored_files": ["/var/log/messages", "/var/log/syslog"]
}
EOF
chown "$EXPORTER_USER:$EXPORTER_USER" "$CONFIG_DIR/app-config.json"
echo -e "${GREEN}[7/8] Creating systemd services...${NC}"
cat > /etc/systemd/system/prometheus-python-exporter.service << EOF
[Unit]
Description=Prometheus Python Application Exporter
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=$EXPORTER_USER
Group=$EXPORTER_USER
ExecStart=$INSTALL_DIR/python-app/venv/bin/python $INSTALL_DIR/python-app/app_exporter.py $PYTHON_PORT
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
cat > /etc/systemd/system/prometheus-go-exporter.service << EOF
[Unit]
Description=Prometheus Go Application Exporter
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=$EXPORTER_USER
Group=$EXPORTER_USER
ExecStart=$INSTALL_DIR/go-app/prometheus-go-exporter $GO_PORT
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable prometheus-python-exporter prometheus-go-exporter
systemctl start prometheus-python-exporter prometheus-go-exporter
echo -e "${GREEN}[8/8] Configuring firewall...${NC}"
if command -v ufw >/dev/null 2>&1; then
ufw allow "$PYTHON_PORT/tcp"
ufw allow "$GO_PORT/tcp"
elif command -v firewall-cmd >/dev/null 2>&1; then
firewall-cmd --permanent --add-port="$PYTHON_PORT/tcp"
firewall-cmd --permanent --add-port="$GO_PORT/tcp"
firewall-cmd --reload
fi
echo -e "${GREEN}Verifying installation...${NC}"
sleep 5
if systemctl is-active --quiet prometheus-python-exporter; then
echo -e "${GREEN}✓ Python exporter service is running${NC}"
else
echo -e "${RED}✗ Python exporter service failed${NC}"
fi
if systemctl is-active --quiet prometheus-go-exporter; then
echo -e "${GREEN}✓ Go exporter service is running${NC}"
else
echo -e "${RED}✗ Go exporter service failed${NC}"
fi
if curl -s "http://localhost:$PYTHON_PORT/metrics" >/dev/null; then
echo -e "${GREEN}✓ Python exporter HTTP endpoint responding${NC}"
else
echo -e "${YELLOW}⚠ Python exporter HTTP endpoint not responding${NC}"
fi
if curl -s "http://localhost:$GO_PORT/metrics" >/dev/null; then
echo -e "${GREEN}✓ Go exporter HTTP endpoint responding${NC}"
else
echo -e "${YELLOW}⚠ Go exporter HTTP endpoint not responding${NC}"
fi
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "Python exporter: http://localhost:$PYTHON_PORT/metrics"
echo -e "Go exporter: http://localhost:$GO_PORT/metrics"
echo -e "Service status: systemctl status prometheus-python-exporter prometheus-go-exporter"
echo -e "Logs: journalctl -u prometheus-python-exporter -u prometheus-go-exporter"
Review the script before running. Execute with: bash install.sh