@@ -53,8 +53,12 @@ interface BaseEntry {
5353interface ServiceLogEntry extends BaseEntry {
5454 id : string ;
5555 deploymentId ?: string ;
56- stream : "stdout" | "stderr" ;
56+ stream : "stdout" | "stderr" | "cron" ;
5757 message : string ;
58+ path ?: string ;
59+ status ?: number | null ;
60+ duration ?: number | null ;
61+ error ?: string | null ;
5862}
5963
6064interface RequestEntry extends BaseEntry {
@@ -194,7 +198,7 @@ function buildLogEndpoint(
194198 case "service-logs" :
195199 path = `/api/services/${ props . serviceId } /logs` ;
196200 params . set ( "limit" , "500" ) ;
197- params . set ( "type" , "container" ) ;
201+ params . set ( "type" , "container-cron " ) ;
198202 if ( filterServerId ) params . set ( "serverId" , filterServerId ) ;
199203 break ;
200204 case "requests" :
@@ -271,13 +275,17 @@ function ServiceLogsFilters({
271275 onShowStdoutChange,
272276 showStderr,
273277 onShowStderrChange,
278+ showCron,
279+ onShowCronChange,
274280} : {
275281 levels : Set < LogLevel > ;
276282 onLevelsChange : ( levels : Set < LogLevel > ) => void ;
277283 showStdout : boolean ;
278284 onShowStdoutChange : ( show : boolean ) => void ;
279285 showStderr : boolean ;
280286 onShowStderrChange : ( show : boolean ) => void ;
287+ showCron : boolean ;
288+ onShowCronChange : ( show : boolean ) => void ;
281289} ) {
282290 const toggleLevel = ( level : LogLevel ) => {
283291 const newLevels = new Set ( levels ) ;
@@ -360,6 +368,13 @@ function ServiceLogsFilters({
360368 >
361369 stderr
362370 </ Button >
371+ < Button
372+ variant = { showCron ? "secondary" : "outline" }
373+ size = "sm"
374+ onClick = { ( ) => onShowCronChange ( ! showCron ) }
375+ >
376+ cron
377+ </ Button >
363378 </ div >
364379 </ >
365380 ) ;
@@ -544,13 +559,26 @@ function ServerFilter({
544559 ) ;
545560}
546561
562+ function formatCronMessage ( entry : ServiceLogEntry ) : string {
563+ const parts = [ entry . message ] ;
564+ if ( entry . path ) parts . push ( entry . path ) ;
565+ if ( entry . status != null ) parts . push ( String ( entry . status ) ) ;
566+ if ( entry . duration != null )
567+ parts . push ( `${ Math . round ( Number ( entry . duration ) || 0 ) } ms` ) ;
568+ if ( entry . error ) parts . push ( entry . error ) ;
569+ return parts . join ( " · " ) ;
570+ }
571+
547572function ServiceLogRow ( {
548573 entry,
549574 search,
550575} : {
551576 entry : ServiceLogEntry ;
552577 search : string ;
553578} ) {
579+ if ( entry . stream === "cron" )
580+ return < CronLogRow entry = { entry } search = { search } /> ;
581+
554582 const level = detectLevel ( entry . message ) ;
555583
556584 return (
@@ -592,6 +620,41 @@ function ServiceLogRow({
592620 ) ;
593621}
594622
623+ function CronLogRow ( {
624+ entry,
625+ search,
626+ } : {
627+ entry : ServiceLogEntry ;
628+ search : string ;
629+ } ) {
630+ const failed = ! ! entry . error ;
631+
632+ return (
633+ < div className = "flex flex-col sm:flex-row hover:bg-black/5 dark:hover:bg-white/5 -mx-2 px-2 py-1 sm:py-0.5 group" >
634+ < div className = "flex items-baseline sm:contents" >
635+ < span
636+ className = "shrink-0 w-[70px] text-slate-400 dark:text-slate-600 select-none pr-2 tabular-nums"
637+ title = { formatPreciseDateTime ( entry . timestamp ) }
638+ >
639+ { formatTime ( entry . timestamp ) }
640+ </ span >
641+ < span className = "shrink-0 w-[50px] text-center px-1 rounded text-[10px] mr-2 text-amber-600 dark:text-amber-400 bg-amber-500/10" >
642+ cron
643+ </ span >
644+ </ div >
645+ < span
646+ className = { `break-all whitespace-pre-wrap ${
647+ failed
648+ ? "text-red-600 dark:text-red-400"
649+ : "text-slate-800 dark:text-slate-200"
650+ } `}
651+ >
652+ { highlightMatches ( formatCronMessage ( entry ) , search ) }
653+ </ span >
654+ </ div >
655+ ) ;
656+ }
657+
595658function RequestRow ( {
596659 entry,
597660 search,
@@ -697,7 +760,9 @@ function serializeLogs(
697760 . map ( ( log ) => {
698761 if ( variant === "service-logs" ) {
699762 const entry = log as ServiceLogEntry ;
700- return `[${ entry . timestamp } ] [${ entry . stream } ] ${ entry . message } ` ;
763+ const message =
764+ entry . stream === "cron" ? formatCronMessage ( entry ) : entry . message ;
765+ return `[${ entry . timestamp } ] [${ entry . stream } ] ${ message } ` ;
701766 }
702767 if ( variant === "requests" ) {
703768 const entry = log as RequestEntry ;
@@ -754,6 +819,10 @@ export function LogViewer(props: LogViewerProps) {
754819 "stderr" ,
755820 parseAsBoolean . withDefault ( true ) ,
756821 ) ;
822+ const [ showCron , setShowCron ] = useQueryState (
823+ "cron" ,
824+ parseAsBoolean . withDefault ( true ) ,
825+ ) ;
757826
758827 const [ statusParam , setStatusParam ] = useQueryState (
759828 "status" ,
@@ -931,6 +1000,7 @@ export function LogViewer(props: LogViewerProps) {
9311000 const entry = log as ServiceLogEntry ;
9321001 if ( entry . stream === "stdout" && ! showStdout ) return false ;
9331002 if ( entry . stream === "stderr" && ! showStderr ) return false ;
1003+ if ( entry . stream === "cron" ) return showCron ;
9341004
9351005 const level = detectLevel ( entry . message ) ;
9361006 if ( level && ! levels . has ( level ) ) return false ;
@@ -945,7 +1015,15 @@ export function LogViewer(props: LogViewerProps) {
9451015
9461016 return true ;
9471017 } ) ;
948- } , [ logs , props . variant , levels , showStdout , showStderr , statusFilter ] ) ;
1018+ } , [
1019+ logs ,
1020+ props . variant ,
1021+ levels ,
1022+ showStdout ,
1023+ showStderr ,
1024+ showCron ,
1025+ statusFilter ,
1026+ ] ) ;
9491027 const logCount = logs . length ;
9501028 const filteredLogCount = filteredLogs . length ;
9511029 const newestFilteredLogTimestamp = (
@@ -1083,6 +1161,8 @@ export function LogViewer(props: LogViewerProps) {
10831161 onShowStdoutChange = { setShowStdout }
10841162 showStderr = { showStderr }
10851163 onShowStderrChange = { setShowStderr }
1164+ showCron = { showCron }
1165+ onShowCronChange = { setShowCron }
10861166 />
10871167 ) }
10881168
0 commit comments