phpnexus/cwh
A Monolog handler for Amazon CloudWatch Logs. phpnexus/cwh lets PHP applications send PSR-3 log records to CloudWatch through the AWS SDK, with configurable log groups and streams, batching, and flexible level handling for production logging.
Architecture fit. The package is a Monolog handler, which is the right seam for a Laravel application: Laravel's Log facade, channel configuration, and contextual logging all sit on Monolog, so adopting this is a configuration-level change rather than a framework change. It introduces no PHP extension requirement and no daemon, which keeps it compatible with containerized, read-only-filesystem, and serverless-adjacent deployments where a sidecar (CloudWatch agent, Fluent Bit) is awkward.
The trade-off is that it operates in-process: log records are buffered in PHP memory and flushed over the AWS SDK's HTTP client during the request/worker lifecycle. That is architecturally fine for low-to-moderate volume, but it makes logging part of the application's critical path and failure domain in a way that a host-level agent is not. For a TPM, the question is not "does it fit" (it does) but "do we want application code owning log shipping."
Integration feasibility. High. Wiring is a custom Laravel logging channel plus IAM credentials; a competent engineer can have it emitting to CloudWatch in under a day. The real work is environmental: IAM role vs. long-lived keys, log group naming conventions, retention policies, and preventing duplicate shipping from hosts that already run an agent.
Dependency consideration: it pulls in aws/aws-sdk-php, a large multi-service package. If the application already ships the SDK (S3, SQS, SNS), marginal cost is near zero. If logging is the only AWS dependency, we are adding tens of megabytes of vendor code and a sizable transitive dependency surface for one handler.
Technical risk.
PutLogEvents sequence-token requirement was removed; older handler implementations that still transmit a token can fail with InvalidParameterException. Confirm the pinned release is post-change.aws/aws-sdk-php constraint can collide with pins from other packages. Run a composer why/dry-run resolution against the real application lockfile, not a greenfield skeleton.Key questions for the maintainer/vendor evaluation.
aws/aws-sdk-php major versions are supported, and what is the tested matrix?ThrottlingException and DataAlreadyAcceptedException?Stack fit. Laravel's logging config supports registering an arbitrary Monolog handler on a channel via the monolog driver with handler / handler_with keys, or via a small custom Logger factory. Either route keeps the change inside config/logging.php and an env flag — no application code touches, no service provider fork. The pragmatic configuration is a stack channel: existing file/stdout handler for fast local triage plus the CloudWatch handler for centralized retention and cross-instance query.
Migration path.
/app/{env}/{service}), retention (default 30 days, not "never expire"), and per-environment separation. Provision these out of band via IaC so the application runtime needs only logs:CreateLogStream and logs:PutLogEvents, not logs:CreateLogGroup.error and above to CloudWatch while the primary channel remains authoritative. This bounds cost and blast radius while validating credentials, permissions, and formatting.Compatibility checks before build.
curl/openssl as required by the SDK HTTP handler.Sequencing. This is a two-sprint item, not a one-day item. Sprint 1: IaC for log groups + retention, IAM role, dev and staging channels with error-level only. Sprint 2: production rollout behind a feature flag, cost baseline established, CloudWatch Logs Insights saved queries and one metric filter/alert documented, runbook written. Do not enable for all severities until the cost-per-GB baseline is known.
Alternative to weigh explicitly. If the fleet runs on EC2/ECS with a manageable host count, the CloudWatch agent or a Fluent Bit sidecar delivers the same outcome with zero per-request latency and no SDK dependency. This package wins when we need per-tenant/per-request log group routing, when we cannot modify the host image, or in environments (some PaaS/serverless) where a sidecar is unavailable. A TPM should require that comparison be documented rather than assumed.
Maintenance and support. MIT-licensed, small contributor base, no commercial backing, no SLA. Treat it as internally owned software from day one: pin the version exactly, forbid ^ ranges in the lockfile for this package, and budget for the possibility of an in-house fork if Monolog or SDK majors move and upstream lags. The release cadence appears healthy, but activity is not the same as a support contract. Assign a named owner.
Scaling characteristics. Throughput is bounded by CloudWatch Logs quotas, not by PHP: PutLogEvents is throttled per log stream, so a naive single-stream-per-application design funnels every instance into one throttling bucket. Use per-instance or per-container streams. Batch limits (message size and events per request) must be respected — oversized payloads produce rejected batches, and a handler with weak error handling will drop them silently. For high-volume services, evaluate Kinesis Firehose → CloudWatch as an ingestion buffer rather than direct SDK puts.
Cost. Ingestion and storage are metered per GB, with separate charges for Logs Insights scans. The failure mode here is verbosity: enabling debug in production through this handler can multiply logging spend by an order of magnitude in a week. Gate severity per environment in configuration and add a budget alarm before production enablement. Validate current regional pricing rather than relying on figures quoted in a review.
Failure modes and mitigations.
| Failure | Impact | Mitigation |
|---|---|---|
| Credential/role misconfiguration | All CloudWatch logs lost; possible exceptions surfaced to users if not isolated | Least-privilege role in IaC; handler failures must be swallowed, never propagated to the request |
| Throttling / quota exhaustion | Dropped batches under load, worst during incidents | Per-instance streams; buffered handler; backoff verified; load test before wide rollout |
| Buffered-record loss on fatal/OOM | Missing logs exactly when debugging is hardest | Keep file/stdout channel in the stack; never make CloudWatch the only sink |
| SDK network timeout added to request latency | Elevated p95/p99, queue worker throughput drop | Async/buffered composition; aggressive SDK timeouts; measure latency delta in staging |
| Dependency conflict on SDK upgrade | Blocked framework or security upgrades | Isolate behind the channel config; verify in CI resolution step |
| Runaway ingestion cost | Budget surprise | Retention caps, severity gating, budget alarms, sampled debug logging |
Ramp-up. Engineering effort to first log line is measured in hours; the operational hardening around it — IAM, retention, cost guardrails, alerting, runbook — is the bulk of the work and should be resourced accordingly. Enablement for the on-call team should include CloudWatch Logs Insights query patterns, an understanding that log delivery is eventually consistent and not a transaction log, and a documented fallback to the file channel. Without that, the team gains a new data source but not a new capability.
Verdict. Technically sound and low-friction for a Laravel stack already invested in AWS; the risks are concentrated in dependency weight, in-process shipping semantics, and the absence of vendor support — all manageable with version pinning, a stacked channel design, and IaC-owned log groups. Proceed with a scoped pilot and treat the vendor comparison against agent-based shipping as a required input to the decision, not an afterthought.
How can I help you explore Laravel packages today?