Skip to content

Commit fed5f06

Browse files
author
serverpod_cloud
committed
refactor(cli)!: cfc20ad0ac6bbc29973879eec68af8f1da24e69a
1 parent 1594040 commit fed5f06

6 files changed

Lines changed: 192 additions & 250 deletions

File tree

serverpod_cloud_cli/lib/command_runner/commands/log_command.dart

Lines changed: 38 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import 'package:config/config.dart';
22
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
33
import 'package:serverpod_cloud_cli/shared/exceptions/exit_exceptions.dart';
4-
import 'package:serverpod_cloud_cli/command_runner/helpers/command_options.dart';
4+
import 'package:serverpod_cloud_cli/command_runner/helpers/command_options.dart'
5+
show DateTimeOrDurationOption, ProjectIdOption, UtcOption;
56
import 'package:serverpod_cloud_cli/commands/logs/logs.dart';
67
import 'package:serverpod_cloud_cli/shared/exceptions/cloud_cli_usage_exception.dart';
78

@@ -16,22 +17,17 @@ enum LogOption<V> implements OptionDefinition<V> {
1617
min: 0,
1718
)),
1819
utc(UtcOption()),
19-
recent(DurationOption(
20-
argName: 'recent',
21-
argAbbrev: 'r',
22-
argPos: 0,
23-
helpText:
24-
'Fetch records from the recent period length; s (seconds) by default. '
25-
'Can also be specified as the first argument.',
26-
min: Duration.zero,
27-
)),
28-
until(DateTimeOption(
20+
until(DateTimeOrDurationOption(
2921
argName: 'until',
30-
helpText: 'Fetch records from before this timestamp.',
22+
helpText: 'Fetch records from before this timestamp. Accepts ISO date '
23+
'(e.g. "2024-01-15T10:30:00Z") or relative from now (e.g. "5m", "3h", "1d"). '
24+
'Can also be specified as the first argument.',
3125
)),
32-
since(DateTimeOption(
26+
since(DateTimeOrDurationOption(
3327
argName: 'since',
34-
helpText: 'Fetch records from after this timestamp.',
28+
argPos: 0,
29+
helpText: 'Fetch records from after this timestamp. Accepts ISO date '
30+
'(e.g. "2024-01-15T10:30:00Z") or relative from now (e.g. "5m", "3h", "1d").',
3531
)),
3632
all(FlagOption(
3733
argName: 'all',
@@ -72,24 +68,37 @@ Examples
7268
\$ scloud log
7369
7470
75-
View logs from the last hour.
71+
View logs from the last hour using duration.
7672
7773
\$ scloud log 1h
7874
75+
\$ scloud log --since 1h
7976
80-
View logs since a specific time, you can use the following formats:
77+
78+
View logs since a specific time using ISO date format:
8179
8280
\$ scloud log --since 2025-01-15T14:00:00Z
8381
8482
\$ scloud log --since "2025-01-15 14:00"
8583
8684
\$ scloud log --since 2025-01-15
8785
88-
View logs in a time range.
86+
87+
View logs in a time range using durations:
88+
89+
\$ scloud log --since 1h --until 5m
90+
91+
92+
View logs in a time range using ISO dates:
8993
9094
\$ scloud log --since 2025-01-15 --until 2025-01-16
9195
9296
97+
Mix ISO dates and durations:
98+
99+
\$ scloud log --since 2025-01-15T14:00:00Z --until 30m
100+
101+
93102
Stream logs in real-time.
94103
95104
\$ scloud log --tail
@@ -110,54 +119,33 @@ Examples
110119
final projectId = commandConfig.value(LogOption.projectId);
111120
final limit = commandConfig.value(LogOption.limit);
112121
final inUtc = commandConfig.value(LogOption.utc);
113-
final recentOpt = commandConfig.optionalValue(LogOption.recent);
114-
final untilOpt = commandConfig.optionalValue(LogOption.until);
115-
final sinceOpt = commandConfig.optionalValue(LogOption.since);
122+
final until = commandConfig.optionalValue(LogOption.until);
123+
final since = commandConfig.optionalValue(LogOption.since);
116124
final tailOpt = commandConfig.optionalValue(LogOption.tail);
117125
final internalAllOpt = commandConfig.value(LogOption.all);
118126

119-
final DateTime? before, after;
120-
final anyTimeSpanIsSet =
121-
recentOpt != null || untilOpt != null || sinceOpt != null;
127+
DateTime? defaultSince;
128+
final anyTimeSpanIsSet = until != null || since != null;
122129
if (internalAllOpt) {
123130
if (anyTimeSpanIsSet) {
124131
throw CloudCliUsageException(
125-
'The --all option cannot be combined with '
126-
'--until, --since, or --recent.',
132+
'The --all option cannot be combined with --until or --since.',
127133
);
128134
}
129-
130-
before = null;
131-
after = null;
132135
} else if (tailOpt == true) {
133136
if (anyTimeSpanIsSet) {
134137
throw CloudCliUsageException(
135-
'The --tail option cannot be combined with '
136-
'--until, --since, or --recent.',
138+
'The --tail option cannot be combined with --until or --since.',
137139
);
138140
}
139-
140-
before = null;
141-
after = null;
142-
} else if (untilOpt != null || sinceOpt != null) {
143-
if (recentOpt != null) {
144-
throw CloudCliUsageException(
145-
'The --recent option cannot be combined with '
146-
'--until or --since.',
147-
);
148-
}
149-
150-
before = untilOpt;
151-
after = sinceOpt;
152-
if (before != null && after != null && before.isBefore(after)) {
141+
} else if (until != null || since != null) {
142+
if (until != null && since != null && until.isBefore(since)) {
153143
throw CloudCliUsageException(
154144
'The --until value must be after --since value.',
155145
);
156146
}
157-
} else {
158-
// If no range specified, default to fetch recent logs
159-
before = null;
160-
after = DateTime.now().subtract(recentOpt ?? Duration(minutes: 10));
147+
} else if (until == null && since == null) {
148+
defaultSince = DateTime.now().subtract(Duration(minutes: 10));
161149
}
162150

163151
if (tailOpt == true) {
@@ -181,8 +169,8 @@ Examples
181169
runner.serviceProvider.cloudApiClient,
182170
writeln: logger.line,
183171
projectId: projectId,
184-
before: before,
185-
after: after,
172+
before: until,
173+
after: since ?? defaultSince,
186174
limit: limit,
187175
inUtc: inUtc,
188176
);

serverpod_cloud_cli/lib/command_runner/completion/completion_script_carapace.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,8 @@ commands:
177177
--limit=: "The maximum number of log records to fetch."
178178
-u, --utc: "Display timestamps in UTC timezone instead of local."
179179
--no-utc: "Display timestamps in UTC timezone instead of local."
180-
-r, --recent=: "Fetch records from the recent period length; s (seconds) by default. Can also be specified as the first argument."
181-
--until=: "Fetch records from before this timestamp."
182-
--since=: "Fetch records from after this timestamp."
180+
--until=: "Fetch records from before this timestamp. Accepts ISO date string (e.g., \"2024-01-15T10:30:00Z\") or duration string (e.g., \"5m\", \"3h\", \"1d\"). Can also be specified as the first argument."
181+
--since=: "Fetch records from after this timestamp. Accepts ISO date string (e.g., \"2024-01-15T10:30:00Z\") or duration string (e.g., \"5m\", \"3h\", \"1d\")."
183182
--tail: "Tail the log and get real time updates."
184183
exclusiveFlags:
185184
- [utc, no-utc]

serverpod_cloud_cli/lib/command_runner/completion/completion_script_completely.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ _scloud_completions() {
793793
;;
794794
795795
'log'*)
796-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_scloud_completions_filter "--quiet -q --verbose -v --analytics --no-analytics -a --version --token --project-dir -d --project-config-file --timeout --yes --project -p --limit --utc --no-utc -u --recent -r --until --since --tail")" -- "$cur")
796+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_scloud_completions_filter "--quiet -q --verbose -v --analytics --no-analytics -a --version --token --project-dir -d --project-config-file --timeout --yes --project -p --limit --utc --no-utc -u --until --since --tail")" -- "$cur")
797797
;;
798798
799799
'db'*)

serverpod_cloud_cli/lib/command_runner/helpers/command_options.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,63 @@ class UserEmailOption extends StringOption {
119119
'${argPos == 0 ? ' Can be passed as the first argument.' : argPos == 1 ? ' Can be passed as the second argument.' : ''}',
120120
);
121121
}
122+
123+
class DateTimeOrDurationParser extends ValueParser<DateTime> {
124+
const DateTimeOrDurationParser();
125+
126+
@override
127+
DateTime parse(final String value) {
128+
final result = _parseDateTimeOrDuration(value);
129+
if (result == null) {
130+
throw FormatException(
131+
'Invalid value: expected ISO date string (e.g., "2024-01-15T10:30:00Z") '
132+
'or duration string (e.g., "5m", "3h", "1d"). Value was: "$value"',
133+
);
134+
}
135+
return result;
136+
}
137+
138+
DateTime? _parseDateTimeOrDuration(final String value) {
139+
try {
140+
return const DateTimeParser().parse(value);
141+
} on FormatException {
142+
final duration = _tryParseDuration(value);
143+
if (duration != null) {
144+
return DateTime.now().subtract(duration);
145+
}
146+
return null;
147+
}
148+
}
149+
150+
Duration? _tryParseDuration(final String value) {
151+
try {
152+
return const DurationParser().parse(value);
153+
} on FormatException {
154+
return null;
155+
}
156+
}
157+
}
158+
159+
class DateTimeOrDurationOption extends ComparableValueOption<DateTime> {
160+
const DateTimeOrDurationOption({
161+
super.argName,
162+
super.argAliases,
163+
super.argAbbrev,
164+
super.argPos,
165+
super.envName,
166+
super.configKey,
167+
super.fromCustom,
168+
super.fromDefault,
169+
super.defaultsTo,
170+
super.helpText,
171+
super.valueHelp = 'YYYY-MM-DDtHH:MM:SSz or duration[us|ms|s|m|h|d]',
172+
super.allowedHelp,
173+
super.group,
174+
super.allowedValues,
175+
super.customValidator,
176+
super.mandatory,
177+
super.hide,
178+
super.min,
179+
super.max,
180+
}) : super(valueParser: const DateTimeOrDurationParser());
181+
}

0 commit comments

Comments
 (0)