Skip to content

Commit 7d6de9a

Browse files
authored
[Prism] Fix two kinds of errors when using json log format. (#36020)
* Fix two kinds of errors when using json log format. * Refactor log filter logic. Override expansion service logger name. * Fix lints * Address the issues found by gemini.
1 parent 4289ea2 commit 7d6de9a

8 files changed

Lines changed: 79 additions & 17 deletions

File tree

sdks/go/pkg/beam/runners/prism/internal/engine/elementmanager.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ type PColInfo struct {
8989
KeyDec func(io.Reader) []byte
9090
}
9191

92+
func (info PColInfo) LogValue() slog.Value {
93+
return slog.GroupValue(
94+
slog.String("GlobalID", info.GlobalID),
95+
slog.String("WindowCoder", info.WindowCoder.String()),
96+
// Do not attempt to log functions, or it will result in JSON marshaling error.
97+
)
98+
}
99+
92100
// WinCoderType indicates what kind of coder
93101
// the window is using. There are only 3
94102
// valid single window encodings.
@@ -110,6 +118,19 @@ const (
110118
WinCustom
111119
)
112120

121+
func (wct WinCoderType) String() string {
122+
switch wct {
123+
case WinGlobal:
124+
return "WinGlobal"
125+
case WinInterval:
126+
return "WinInterval"
127+
case WinCustom:
128+
return "WinCustom"
129+
default:
130+
return fmt.Sprintf("Unknown(%d)", wct)
131+
}
132+
}
133+
113134
// ToData recodes the elements with their approprate windowed value header.
114135
func (es elements) ToData(info PColInfo) [][]byte {
115136
var ret [][]byte
@@ -338,7 +359,7 @@ func (rb RunBundle) LogValue() slog.Value {
338359
return slog.GroupValue(
339360
slog.String("ID", rb.BundleID),
340361
slog.String("stage", rb.StageID),
341-
slog.Time("watermark", rb.Watermark.ToTime()))
362+
slog.Any("watermark", rb.Watermark))
342363
}
343364

344365
// Bundles is the core execution loop. It produces a sequences of bundles able to be executed.

sdks/go/pkg/beam/runners/prism/internal/environments.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"os"
2525
"os/exec"
2626
"slices"
27+
"strconv"
2728
"time"
2829

2930
fnpb "github.com/apache/beam/sdks/v2/go/pkg/beam/model/fnexecution_v1"
@@ -259,7 +260,12 @@ func dockerEnvironment(ctx context.Context, logger *slog.Logger, dp *pipepb.Dock
259260
defer rc.Close()
260261
var buf bytes.Buffer
261262
stdcopy.StdCopy(&buf, &buf, rc)
262-
logger.Info("container being killed", slog.Any("cause", context.Cause(ctx)), slog.String("containerLog", buf.String()))
263+
logger.Info("container being killed", slog.Any("cause", context.Cause(ctx)))
264+
msgs, err := strconv.Unquote(buf.String())
265+
if err != nil {
266+
msgs = buf.String()
267+
}
268+
logger.Debug("container log", "log", msgs)
263269
}
264270
// Can't use command context, since it's already canceled here.
265271
if err := cli.ContainerKill(bgctx, containerID, ""); err != nil {

sdks/go/pkg/beam/runners/prism/internal/execute.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,11 @@ func executePipeline(ctx context.Context, wks map[string]*worker.W, j *jobservic
360360
case rb, ok := <-bundles:
361361
if !ok {
362362
err := eg.Wait()
363-
j.Logger.Debug("pipeline done!", slog.String("job", j.String()), slog.Any("error", err), slog.Any("topo", topo))
363+
var topoAttrs []any
364+
for _, s := range topo {
365+
topoAttrs = append(topoAttrs, slog.Any(s.ID, s))
366+
}
367+
j.Logger.Debug("pipeline done!", slog.String("job", j.String()), slog.Any("error", err), slog.Group("topo", topoAttrs...))
364368
return err
365369
}
366370
eg.Go(func() error {

sdks/go/pkg/beam/runners/prism/internal/stage.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ func clampTick(dur time.Duration) time.Duration {
108108
}
109109
}
110110

111+
func (s *stage) LogValue() slog.Value {
112+
var outAttrs []any
113+
for k, v := range s.OutputsToCoders {
114+
outAttrs = append(outAttrs, slog.Any(k, v))
115+
}
116+
return slog.GroupValue(
117+
slog.String("ID", s.ID),
118+
slog.Any("transforms", s.transforms),
119+
slog.Any("inputInfo", s.inputInfo),
120+
slog.Group("outputInfo", outAttrs...),
121+
)
122+
}
123+
111124
func (s *stage) Execute(ctx context.Context, j *jobservices.Job, wk *worker.W, comps *pipepb.Components, em *engine.ElementManager, rb engine.RunBundle) (err error) {
112125
if s.baseProgTick.Load() == nil {
113126
s.baseProgTick.Store(minimumProgTick)

sdks/python/apache_beam/runners/portability/job_server.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# pytype: skip-file
1919

2020
import atexit
21+
import logging
2122
import shutil
2223
import signal
2324
import tempfile
@@ -102,6 +103,7 @@ class SubprocessJobServer(JobServer):
102103
def __init__(self):
103104
self._local_temp_root = None
104105
self._server = None
106+
self._log_filter = None
105107

106108
def subprocess_cmd_and_endpoint(self):
107109
raise NotImplementedError(type(self))
@@ -111,8 +113,11 @@ def start(self):
111113
self._local_temp_root = tempfile.mkdtemp(prefix='beam-temp')
112114
cmd, endpoint = self.subprocess_cmd_and_endpoint()
113115
port = int(endpoint.split(':')[-1])
116+
logger = logging.getLogger(f"{self.__class__.__name__}")
117+
if self._log_filter is not None:
118+
logger.addFilter(self._log_filter)
114119
self._server = subprocess_server.SubprocessServer(
115-
beam_job_api_pb2_grpc.JobServiceStub, cmd, port=port)
120+
beam_job_api_pb2_grpc.JobServiceStub, cmd, port=port, logger=logger)
116121
return self._server.start()
117122

118123
def stop(self):

sdks/python/apache_beam/runners/portability/prism_runner.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
from apache_beam.transforms import environments
4545
from apache_beam.utils import shared
4646
from apache_beam.utils import subprocess_server
47-
from apache_beam.utils.subprocess_server import _LOGGER as subprocess_server_logger
4847
from apache_beam.version import __version__ as beam_version
4948

5049
# pytype: skip-file
@@ -120,9 +119,7 @@ class PrismRunnerLogFilter(logging.Filter):
120119
def filter(self, record):
121120
if record.funcName == 'log_stdout':
122121
try:
123-
# TODO: Fix this error message from prism
124-
message = record.getMessage().replace(
125-
'"!ERROR:time.Time year outside of range [0,9999]"', '')
122+
message = record.getMessage()
126123
json_record = json.loads(message)
127124
record.levelno = getattr(logging, json_record["level"])
128125
record.levelname = logging.getLevelName(record.levelno)
@@ -148,7 +145,11 @@ def filter(self, record):
148145
record.msg = (
149146
f"{json_record['msg']} "
150147
f"({', '.join(f'{k}={v!r}' for k, v in extras.items())})")
151-
except (json.JSONDecodeError, KeyError, ValueError):
148+
except (json.JSONDecodeError,
149+
KeyError,
150+
ValueError,
151+
TypeError,
152+
AttributeError):
152153
# The log parsing/filtering is best-effort.
153154
pass
154155

@@ -181,7 +182,7 @@ def __init__(self, options):
181182
# override console to json with log filter enabled
182183
if self._log_kind == "console":
183184
self._log_kind = "json"
184-
subprocess_server_logger.addFilter(PrismRunnerLogFilter())
185+
self._log_filter = PrismRunnerLogFilter()
185186

186187
# the method is only kept for testing and backward compatibility
187188
@classmethod

sdks/python/apache_beam/transforms/external.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,8 @@ def __enter__(self):
10971097
ExpansionAndArtifactRetrievalStub,
10981098
self.path_to_jar,
10991099
self._extra_args,
1100-
classpath=classpath_urls)
1100+
classpath=classpath_urls,
1101+
logger="ExpansionService")
11011102
self._service = self._service_provider.__enter__()
11021103
self._service_count += 1
11031104
return self._service

sdks/python/apache_beam/utils/subprocess_server.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class SubprocessServer(object):
132132
with SubprocessServer(GrpcStubClass, [executable, arg, ...]) as stub:
133133
stub.CallService(...)
134134
"""
135-
def __init__(self, stub_class, cmd, port=None):
135+
def __init__(self, stub_class, cmd, port=None, logger=None):
136136
"""Creates the server object.
137137
138138
:param stub_class: the auto-generated GRPC client stub class used for
@@ -143,12 +143,21 @@ def __init__(self, stub_class, cmd, port=None):
143143
service. If not given, one will be randomly chosen and the special
144144
string "{{PORT}}" will be substituted in the command line arguments
145145
with the chosen port.
146+
:param logger: (optional) The logger or logger name to use for the
147+
subprocess's stderr and stdout. If not given, the current module logger
148+
would be used.
146149
"""
147150
self._owner_id = None
148151
self._stub_class = stub_class
149152
self._cmd = [str(arg) for arg in cmd]
150153
self._port = port
151154
self._grpc_channel = None
155+
if isinstance(logger, str):
156+
self._logger = logging.getLogger(logger)
157+
elif isinstance(logger, logging.Logger):
158+
self._logger = logger
159+
else:
160+
self._logger = _LOGGER
152161

153162
@classmethod
154163
@contextlib.contextmanager
@@ -203,9 +212,9 @@ def start_process(self):
203212
if self._owner_id is not None:
204213
self._cache.purge(self._owner_id)
205214
self._owner_id = self._cache.register()
206-
return self._cache.get(tuple(self._cmd), self._port)
215+
return self._cache.get(tuple(self._cmd), self._port, self._logger)
207216

208-
def _really_start_process(cmd, port):
217+
def _really_start_process(cmd, port, logger):
209218
if not port:
210219
port, = pick_port(None)
211220
cmd = [arg.replace('{{PORT}}', str(port)) for arg in cmd] # pylint: disable=not-an-iterable
@@ -220,7 +229,7 @@ def log_stdout():
220229
while line:
221230
# The log obtained from stdout is bytes, decode it into string.
222231
# Remove newline via rstrip() to not print an empty line.
223-
_LOGGER.info(line.decode(errors='backslashreplace').rstrip())
232+
logger.info(line.decode(errors='backslashreplace').rstrip())
224233
line = process.stdout.readline()
225234

226235
t = threading.Thread(target=log_stdout)
@@ -283,15 +292,17 @@ def __init__(
283292
path_to_jar,
284293
java_arguments,
285294
classpath=None,
286-
cache_dir=None):
295+
cache_dir=None,
296+
logger=None):
287297
self._java_path = JavaHelper.get_java()
288298
if classpath:
289299
# java -jar ignores the classpath, so we make a new jar that embeds
290300
# the requested classpath.
291301
path_to_jar = self.make_classpath_jar(path_to_jar, classpath, cache_dir)
292302
super().__init__(
293303
stub_class,
294-
[self._java_path, '-jar', path_to_jar] + list(java_arguments))
304+
[self._java_path, '-jar', path_to_jar] + list(java_arguments),
305+
logger=logger)
295306
self._existing_service = path_to_jar if is_service_endpoint(
296307
path_to_jar) else None
297308

0 commit comments

Comments
 (0)