AWS → STACKIT · LOGGING AND MONITORING
CloudWatch logging and monitoring on STACKIT
CloudWatch bundles log groups, log-derived metrics, alarms and notifications. On STACKIT these become a Logs instance, an Observability instance with alert rules, and receivers for e-mail. StackToIT drafts the instances and the alert group in Terraform and lists, file by file, what a person still writes: the log labels, the alert expressions and the mail relay.
The migration in seven steps
The order follows the estate that StackToIT assessed for this page: two log groups, one metric filter, two alarms, one SNS topic with an e-mail subscription and one CloudWatch board.
List what the estate really uses
Read the log groups with their retention, the metric filters, the alarms and the topics behind them. A metric filter or alarm that nobody reads is cheaper to retire than to rebuild. The assessment lists each log group, alarm and topic and the board as its own unit; the metric filter and the e-mail subscription sit inside the units they belong to.
Create the Logs instances
Log groups that keep their logs for the same number of days share one Logs instance. Retention is a setting of the instance, not of a group, so the 30-day group and the 365-day group landed on two instances.
Choose a label convention and a write token
STACKIT Logs has no log group or stream object. The group name survives only as a label that your shipper sends. Create a write access token per instance and point the shipper at the instance’s ingest address (Loki API or OpenTelemetry).
Rebuild log-derived metrics
A Logs instance has no metric filter. Turn the filter into a log alert rule in Observability, or let the application publish the metric itself.
Write the alert expressions
The alert group keeps each alarm’s name and its “for” duration. The expression is a required variable, because no CloudWatch metric is known to exist in your Observability instance.
Set up receivers
The e-mail subscription of the SNS topic becomes a receiver and a route in the alert configuration of the Observability instance. The address and the SMTP relay are variables you fill in.
Run both in parallel, then retire
Ship logs to both targets, compare alert behaviour on real incidents, and switch off the CloudWatch side after acceptance.
Log groups, a metric filter and two alarms
The input was written for this page and assessed by StackToIT on 4 November 2026. Left: the input from the audit log group to the latency alarm. Right: blocks of the STACKIT Terraform the engine wrote, with the review comments that sit above each block in the file left out.
resource "aws_cloudwatch_log_group" "audit" {
name = "/acme/audit"
retention_in_days = 365
}
resource "aws_cloudwatch_log_metric_filter" "errors" {
name = "orders-api-errors"
log_group_name = aws_cloudwatch_log_group.api.name
pattern = "ERROR"
metric_transformation {
name = "OrdersApiErrors"
namespace = "Acme/Orders"
value = "1"
}
}
resource "aws_sns_topic" "oncall" {
name = "acme-oncall"
}
resource "aws_sns_topic_subscription" "oncall_mail" {
topic_arn = aws_sns_topic.oncall.arn
protocol = "email"
endpoint = "oncall@example.com"
}
resource "aws_cloudwatch_metric_alarm" "errors_high" {
alarm_name = "orders-api-errors-high"
namespace = "Acme/Orders"
metric_name = "OrdersApiErrors"
statistic = "Sum"
period = 300
evaluation_periods = 1
threshold = 10
comparison_operator = "GreaterThanThreshold"
alarm_actions = [aws_sns_topic.oncall.arn]
}
resource "aws_cloudwatch_metric_alarm" "latency_high" {
alarm_name = "orders-alb-latency-high"
namespace = "AWS/ApplicationELB"
metric_name = "TargetResponseTime"
statistic = "Average"
period = 60
evaluation_periods = 5
threshold = 1.5
comparison_operator = "GreaterThanThreshold"
alarm_actions = [aws_sns_topic.oncall.arn]
}resource "stackit_logs_instance" "logs_retention_180d" {
project_id = var.project_id
display_name = "logs-retention-180d"
retention_days = 180
}
resource "stackit_observability_alertgroup" "migrated" {
project_id = var.project_id
instance_id = stackit_observability_instance.migrated.instance_id
name = "migrated-alarms"
rules = [{
alert = "errors-high"
expression = var.alarm_errors_high_expression
labels = {
source_alarm = "aws_cloudwatch_metric_alarm.errors_high"
notify_receiver = "sns-oncall"
}
annotations = {
summary = "orders-api-errors-high"
}
}, {
alert = "latency-high"
expression = var.alarm_latency_high_expression
for = "240s"
labels = {
source_alarm = "aws_cloudwatch_metric_alarm.latency_high"
notify_receiver = "sns-oncall"
}
annotations = {
summary = "orders-alb-latency-high"
}
}]
}365 days become 180
The audit group keeps its logs for 365 days. A STACKIT Logs instance keeps at most 180 days, so the draft sets 180 and records a decision: keep it at that, or also stream the logs to long-term storage.
The “for” duration is derived, the expression is not
The latency alarm evaluates five one-minute periods. The draft takes (periods minus one) × period, so the rule carries for = 240s; the error alarm, with a single period, gets no for. Metric, comparison and threshold are not translated; they go into the variable you supply.
Labels route notifications
Each rule carries a notify_receiver label that the route of the Observability instance matches against the e-mail receiver.
What each CloudWatch resource became
Read from the run’s assessment and Terraform. All six units are classed RECONFIGURE; no unit was left unmapped.
| CloudWatch resource | STACKIT target | What a person still does |
|---|---|---|
| aws_cloudwatch_log_group (two) | STACKIT Logs instance, one per retention period | Choose the label, create the write token and access list, point the shipper at the ingest address |
| aws_cloudwatch_log_metric_filter | No equivalent in Logs; named as not carried | Rebuild as a log alert rule or as an application metric |
| aws_cloudwatch_metric_alarm (two) | Rules in one Observability alert group | Write the PromQL expression for each rule against metrics that exist in your instance |
| aws_sns_topic and e-mail subscription | Receiver and route in the alert configuration of the Observability instance | Enter the recipient address and the SMTP relay; other subscription types need their own receiver |
| CloudWatch board resource | Grafana in the Observability instance (confidence LOW) | Rebuild the panels in Grafana; the board body is not carried |
Both the metric filter and the SNS subscription are absorbed into the units they belong to. They do not appear as separate units, but the draft names what is not carried for each.
The Observability instance with its receiver
The receiver for the SNS e-mail subscription, as written in the same file. The recipient address is a variable: the engine does not copy a personal address from the source.
resource "stackit_observability_instance" "migrated" {
project_id = var.project_id
name = "migrated-observability"
plan_name = var.observability_plan_name
alert_config = {
receivers = [{
name = "default"
}, {
name = "sns-oncall"
email_configs = [{
to = var.alert_mail_to_oncall_mail
from = var.alert_mail_from
smart_host = var.alert_mail_smart_host
send_resolved = false
}]
}]
route = {
receiver = "default"
routes = [{
receiver = "sns-oncall"
matchers = ["notify_receiver=\"sns-oncall\""]
}]
}
}
}Decisions your architects own
Retention beyond 180 days
Audit logs older than 180 days exist only if you archive them, for example in a separate Object Storage bucket. STACKIT’s own FAQ recommends that cold-storage approach for long retention.
Label key for the group name
The plan chooses no label key and reconfigures no producer. Pick one convention for all log sources before the first line is shipped.
Alert semantics
CloudWatch evaluates statistic, period and datapoints. A PromQL rule evaluates differently, so check each rule against a real incident, not only against a syntax check.
Mail relay and credentials
The draft writes no SMTP login, and it generates no Logs access token, because the token is a credential that ends up in the state. Decide where both live.
Boards and history
Panels are rebuilt by hand. Historic logs and metrics are not copied by the draft; decide whether anyone needs them after cutover.
Percentile and anomaly alarms
This estate has none. If yours does, check that Prometheus can express them before you promise parity.
Questions operations teams ask
Can I keep my CloudWatch agent?
No. The agent sends to CloudWatch. On STACKIT you ship logs with a collector that speaks the Loki API or OpenTelemetry. STACKIT’s Logs documentation has how-tos for Vector, Fluent Bit, the OpenTelemetry Collector and Grafana Alloy.
Do metric filters carry over?
No. A CloudWatch metric filter turns matching log events into a metric, and it only counts events after the filter exists. STACKIT Logs has no such object, so the run lists it as not carried and points to a log alert rule or an application metric.
How long can STACKIT Logs keep my logs?
The STACKIT Logs FAQ states that individual retention beyond 180 days is not supported. For longer periods you stream a copy to cheaper long-term storage in parallel.
Does StackToIT move the existing log history?
No. StackToIT changes nothing in any cloud and copies no data. It drafts the targets and writes the steps your team carries out.
Sources
Your next move: STACKIT.
Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.