Python Automation and Monitoring Questions
Focuses on using the Python programming language to automate operational tasks and implement monitoring workflows. Includes writing robust scripts and small applications for file input and output, subprocess management, making HTTP requests and handling responses, parsing and producing JSON, structured error handling and retries, logging and alerting, and clear code organization and packaging. Discuss when to choose Python over shell scripts for complexity, portability, error handling, and maintainability. Covers libraries and tooling for scheduling and background jobs, interacting with cloud provider software development kits, instrumenting applications for metrics and traces, integrating with monitoring stacks, and basic concurrency and asynchronous programming patterns when needed. Also includes testing and validation of automation scripts, secure handling of credentials and configuration, and deployment practices for operational scripts.
Sample Answer
import asyncio
import docker, json, time
from datetime import datetime
client = docker.from_env()
async def watch_events(queue, checkpoint):
loop = asyncio.get_event_loop()
for event in client.events(decode=True):
if event.get("Type")=="container" and event.get("Action") in ("start","restart"):
cid = event["id"][:12]
asyncio.create_task(stream_container_logs(cid, queue, checkpoint))def stream_container_logs(container_id, queue, checkpoint):
c = client.containers.get(container_id)
ck = checkpoint.get(container_id) or {"since": 0}
since = ck["since"]
# docker-py blocking generator
for raw in c.logs(stream=True, since=since, timestamps=True, follow=True):
# raw is bytes like b'2025-11-22T... log line'
ts_str, _, line = raw.decode().partition(' ')
ts = datetime.fromisoformat(ts_str).timestamp()
record = {"container": container_id, "ts": ts, "line": line.rstrip()}
# synchronous put into asyncio queue
asyncio.get_event_loop().call_soon_threadsafe(asyncio.create_task, queue.put(record))
checkpoint[container_id] = {"since": ts}import aiohttp, aiosqlite
class Forwarder:
def __init__(self, queue, buffer_db='buffer.db', max_q=1000):
self.queue = queue
self.sem = asyncio.Semaphore(10) # concurrency to ingestion
self.buffer_db = buffer_db
async def start(self):
self.session = aiohttp.ClientSession()
await self._init_db()
asyncio.create_task(self._drain_buffer())
while True:
rec = await self.queue.get()
try:
await self._send_with_backoff(rec)
except Exception:
await self._persist(rec)
self.queue.task_done()
async def _send_with_backoff(self, rec):
async with self.sem:
for attempt in range(6):
try:
async with self.session.post("https://ingest.example/api", json=rec, timeout=10) as r:
if r.status==200:
return
elif 429 == r.status:
await asyncio.sleep(2**attempt)
else:
r.raise_for_status()
except Exception:
await asyncio.sleep(2**attempt)
raise RuntimeError("failed after retries")
async def _init_db(self):
async with aiosqlite.connect(self.buffer_db) as db:
await db.execute("CREATE TABLE IF NOT EXISTS buffer(id INTEGER PRIMARY KEY, payload TEXT, ts REAL)")
await db.commit()
async def _persist(self, rec):
async with aiosqlite.connect(self.buffer_db) as db:
await db.execute("INSERT INTO buffer(payload,ts) VALUES (?,?)", (json.dumps(rec), rec["ts"]))
await db.commit()
async def _drain_buffer(self):
while True:
async with aiosqlite.connect(self.buffer_db) as db:
async for row in db.execute("SELECT id,payload FROM buffer ORDER BY id LIMIT 100"):
id_, payload = row
rec = json.loads(payload)
try:
await self._send_with_backoff(rec)
await db.execute("DELETE FROM buffer WHERE id=?", (id_,))
await db.commit()
except Exception:
break
await asyncio.sleep(5)import json
def save_checkpoint(path, checkpoint):
with open(path,'w') as f: json.dump(checkpoint,f)
def load_checkpoint(path):
try:
with open(path) as f: return json.load(f)
except: return {}Sample Answer
Sample Answer
import random, time
def backoff(attempt, base=0.5, cap=30):
return min(cap, base * (2**attempt)) * (1 + random.uniform(-0.1, 0.1))
# use per-request attempt counter; never reset erroneouslySample Answer
import os, time, gzip, shutil, fcntl, threading
from datetime import datetime
class RotatingLogger:
def __init__(self, path, max_bytes=10*1024*1024, interval_secs=3600, backup_count=5):
self.path = path
self.max_bytes = max_bytes
self.interval = interval_secs
self.backup = backup_count
self._roll_at = time.time() + self.interval
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
def _acquire_lock(self, fd):
# exclusive, blocking
fcntl.flock(fd, fcntl.LOCK_EX)
def _release_lock(self, fd):
fcntl.flock(fd, fcntl.LOCK_UN)
def _do_rotate(self):
# open file descriptor to lock rotation across processes
with open(self.path, 'a+') as f:
self._acquire_lock(f.fileno())
try:
f.flush()
size = os.stat(self.path).st_size
if size < self.max_bytes and time.time() < self._roll_at:
return
# compute rotated name
ts = datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
rotated = f"{self.path}.{ts}"
# atomic rename (same filesystem)
os.rename(self.path, rotated)
# recreate the original file for writers
open(self.path, 'a').close()
# update next roll time
self._roll_at = time.time() + self.interval
finally:
self._release_lock(f.fileno())
# compress rotated file in background to avoid blocking writers
threading.Thread(target=self._compress, args=(rotated,), daemon=True).start()
self._prune_backups()
def _compress(self, fname):
gzname = fname + ".gz"
with open(fname, 'rb') as src, gzip.open(gzname, 'wb') as dst:
shutil.copyfileobj(src, dst)
os.remove(fname)
def _prune_backups(self):
logs = sorted([p for p in os.listdir(os.path.dirname(self.path) or ".")
if p.startswith(os.path.basename(self.path)+".")])[-self.backup:]
# remove older gz files beyond backup_count
for p in sorted([p for p in os.listdir(os.path.dirname(self.path) or ".")
if p.startswith(os.path.basename(self.path)+".") and not p in logs]):
try: os.remove(os.path.join(os.path.dirname(self.path) or ".", p))
except: pass
def write(self, data):
# writers should open, lock-free, with O_APPEND so writes are atomic (POSIX)
# but small writes are atomic only up to PIPE_BUF; for long lines use file lock briefly
mode = os.O_WRONLY | os.O_APPEND | os.O_CREAT
fd = os.open(self.path, mode, 0o644)
try:
# optional tiny lock to ensure atomic multi-write lines
# fcntl.flock(fd, fcntl.LOCK_SH)
os.write(fd, data.encode())
finally:
os.close(fd)
# check rotate opportunistically
if os.path.getsize(self.path) >= self.max_bytes or time.time() >= self._roll_at:
self._do_rotate()Sample Answer
from opentelemetry import trace, propagators
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.sdk.trace import TracerProvider
tracer = trace.get_tracer(__name__)
propagator = TraceContextTextMapPropagator()
# Inject into outgoing HTTP headers
with tracer.start_as_current_span("outgoing-call") as span:
headers = {}
propagator.inject(headers)
# use headers with requests or httpx
# requests.get(url, headers=headers)
# Extract incoming HTTP headers
def handle_request(request_headers):
ctx = propagator.extract(carrier=request_headers)
with tracer.start_as_current_span("server-handler", context=ctx):
# handle request
pass# When producing
msg_headers = {}
propagator.inject(msg_headers)
producer.send(topic, value=payload, headers=list(msg_headers.items()))
# When consuming
def on_message(msg):
headers = dict(msg.headers)
ctx = propagator.extract(headers)
with tracer.start_as_current_span("consumer-process", context=ctx):
process(msg)# Parent
env = os.environ.copy()
propagator.inject(env) # inject into env keys like TRACEPARENT
subprocess.run(["python", "worker.py"], env=env)
# Child: extract from os.environ
ctx = propagator.extract(os.environ)
with tracer.start_as_current_span("child-work", context=ctx):
...from opentelemetry.trace import get_current_span
span = get_current_span()
trace_id = format(span.get_span_context().trace_id, '032x')
logger.info("step", extra={"trace_id": trace_id})Unlock Full Question Bank
Get access to hundreds of Python Automation and Monitoring interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.