1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
media / base / media_log.h [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_BASE_MEDIA_LOG_H_
#define MEDIA_BASE_MEDIA_LOG_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include "base/gtest_prod_util.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/thread_annotations.h"
#include "build/build_config.h"
#include "media/base/buffering_state.h"
#include "media/base/media_export.h"
#include "media/base/media_log_events.h"
#include "media/base/media_log_message_levels.h"
#include "media/base/media_log_properties.h"
#include "media/base/media_log_record.h"
#include "media/base/pipeline_status.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_APPLE)
#include "base/apple/osstatus_logging.h"
#endif // BUILDFLAG(IS_APPLE)
namespace media {
// Interface for media components to log to chrome://media-internals log.
//
// To provide a logging implementation, derive from MediaLog instead.
//
// Implementations only need to implement AddLogRecordLocked(), which must be
// thread safe in the sense that it may be called from multiple threads, though
// it will not be called concurrently. See below for more details.
//
// Implementations should also call InvalidateLog during destruction, to signal
// to any child logs that the underlying log is no longer available.
class MEDIA_EXPORT MediaLog {
public:
static const char kEventKey[];
// Maximum limit for the total number of logs kept per renderer. At the time of
// writing, 512 events of the kind: { "property": value } together consume ~88kb
// of memory on linux.
#if BUILDFLAG(IS_ANDROID)
static constexpr size_t kLogLimit = 128;
#else
static constexpr size_t kLogLimit = 512;
#endif
MediaLog(const MediaLog&) = delete;
MediaLog& operator=(const MediaLog&) = delete;
// Constructor is protected, see below.
virtual ~MediaLog();
// Report a log message at the specified log level.
void AddMessage(MediaLogMessageLevel level, std::string message);
// Typechecked property setter, since all properties must take values.
// For example, MediaLogProperty::kResolution supports only gfx::Size as
// an argument (see media_log_properties.h for this), so calling
// media_log->SetProperty<MediaLogProperty::kResolution>(1);
// would lead to a compile error, while
// gfx::Size rect = {100, 100};
// media_log->SetProperty<MediaLogProperty::kResolution>(rect);
// is correct.
template <MediaLogProperty P, typename T>
void SetProperty(const T& value) {
AddLogRecord(CreatePropertyRecord<P, T>(value));
}
// TODO(tmathmeyer) add the ability to report events with a separated
// start and end time.
// Send an event to the media log that may or may not have attached data.
// For example, MediaLogEvent::kPlay takes no arguments, while
// MediaLogEvent::kSeek takes a double as an argument, representing the time.
// A proper way to add either of these events would be
// media_log->AddEvent<MediaLogEvent::kPlay>();
// media_log->AddEvent<MediaLogEvent::kSeek>(1.99);
template <MediaLogEvent E, typename... T>
void AddEvent(const T&... value) {
std::unique_ptr<MediaLogRecord> record = CreateEventRecord<E, T...>();
MediaLogEventTypeSupport<E, T...>::AddExtraData(&record->params, value...);
AddLogRecord(std::move(record));
}
// Notify a non-ok Status. This method Should _not_ be given an OK status.
template <typename T>
void NotifyError(const TypedStatus<T>& status) {
DCHECK(!status.is_ok());
std::unique_ptr<MediaLogRecord> record =
CreateRecord(MediaLogRecord::Type::kMediaStatus);
base::Value serialized = MediaSerialize(status.data_);
DCHECK(serialized.is_dict());
if (DCHECK_IS_ON() && DLOG_IS_ON(ERROR) && ShouldLogToDebugConsole()) {
EmitConsoleErrorLog(serialized.GetDict().Clone());
}
record->params.Merge(std::move(serialized.GetDict()));
AddLogRecord(std::move(record));
}
// Notify the media log that the player is destroyed. Some implementations
// will want to change event handling based on this.
void OnWebMediaPlayerDestroyed();
// Returns a string usable as the contents of a MediaError.message.
// This method returns an incomplete message if it is called before the
// pertinent events for the error have been added to the log.
// Note: The base class definition only produces empty messages. See
// RenderMediaLog for where this method is meaningful.
// Inheritors should override GetErrorMessageLocked().
// TODO(tmathmeyer) Use a media::Status when that is ready.
std::string GetErrorMessage();
// Add a record to this log. Inheritors should override AddLogRecordLocked to
// do something. This needs to be public for MojoMediaLogService to use it.
void AddLogRecord(std::unique_ptr<MediaLogRecord> event);
// Provide a MediaLog which can have a separate lifetime from this one, but
// still write to the same player's log. It is not guaranteed that this will
// log forever; it might start silently discarding log messages if the
// original log is closed by whoever owns it. However, it's safe to use it
// even if this occurs, in the "won't crash" sense.
virtual std::unique_ptr<MediaLog> Clone();
// Can be used for stopping a MediaLog during a garbage-collected destruction
// sequence.
virtual void Stop();
// Returns true if logs should be emitted to the console in debug mode. Some
// subclasses will disable this.
virtual bool ShouldLogToDebugConsole() const;
protected:
// Ensures only subclasses and factories (e.g. Clone()) can create MediaLog.
MediaLog();
// Methods that may be overridden by inheritors. All calls may arrive on any
// thread, but will be synchronized with respect to any other *Locked calls on
// any other thread, and with any parent log invalidation.
//
// Please see the documentation for the corresponding public methods.
virtual void AddLogRecordLocked(std::unique_ptr<MediaLogRecord> event);
virtual void OnWebMediaPlayerDestroyedLocked();
virtual std::string GetErrorMessageLocked();
// MockMediaLog also needs to call this method.
template <MediaLogProperty P, typename T>
std::unique_ptr<MediaLogRecord> CreatePropertyRecord(const T& value) {
auto record = CreateRecord(MediaLogRecord::Type::kMediaPropertyChange);
record->params.Set(MediaLogPropertyKeyToString(P),
MediaLogPropertyTypeSupport<P, T>::Convert(value));
return record;
}
template <MediaLogEvent E, typename... Opt>
std::unique_ptr<MediaLogRecord> CreateEventRecord() {
std::unique_ptr<MediaLogRecord> record(
CreateRecord(MediaLogRecord::Type::kMediaEventTriggered));
record->params.Set(MediaLog::kEventKey,
MediaLogEventTypeSupport<E, Opt...>::TypeName());
return record;
}
// Notify all child logs that they should stop working. This should be called
// to guarantee that no further calls into AddLogRecord should be allowed.
// Further, since calls into this log may happen on any thread, it's important
// to call this while the log is still in working order. For example, calling
// it immediately during destruction is a good idea.
void InvalidateLog();
struct ParentLogRecord : base::RefCountedThreadSafe<ParentLogRecord> {
explicit ParentLogRecord(MediaLog* log);
ParentLogRecord(const ParentLogRecord&) = delete;
ParentLogRecord& operator=(const ParentLogRecord&) = delete;
// |lock_| protects the rest of this structure.
base::Lock lock;
// Original media log, or null.
raw_ptr<MediaLog> media_log GUARDED_BY(lock) = nullptr;
protected:
friend class base::RefCountedThreadSafe<ParentLogRecord>;
virtual ~ParentLogRecord();
};
private:
// Allows MediaLogTest to construct MediaLog directly for testing.
friend class MediaLogTest;
FRIEND_TEST_ALL_PREFIXES(MediaLogTest, EventsAreForwarded);
FRIEND_TEST_ALL_PREFIXES(MediaLogTest, EventsAreNotForwardedAfterInvalidate);
// Use |parent_log_record| instead of making a new one.
explicit MediaLog(scoped_refptr<ParentLogRecord> parent_log_record);
// Helper methods to create events and their parameters.
std::unique_ptr<MediaLogRecord> CreateRecord(MediaLogRecord::Type type);
// Helper method for emitting error logs to console.
void EmitConsoleErrorLog(base::Value::Dict status_dict);
// The underlying media log.
scoped_refptr<ParentLogRecord> parent_log_record_;
};
// Helper class to make it easier to use MediaLog like DVLOG().
class MEDIA_EXPORT LogHelper {
public:
LogHelper(MediaLogMessageLevel level,
MediaLog* media_log,
const char* file,
int line,
std::optional<logging::SystemErrorCode> code = std::nullopt);
LogHelper(MediaLogMessageLevel level,
const std::unique_ptr<MediaLog>& media_log,
const char* file,
int line,
std::optional<logging::SystemErrorCode> code = std::nullopt);
~LogHelper();
std::ostream& stream() { return stream_; }
private:
const char* file_;
const int line_;
const MediaLogMessageLevel level_;
const raw_ptr<MediaLog> media_log_;
const std::optional<logging::SystemErrorCode> code_;
std::stringstream stream_;
};
// Provides a stringstream to collect a log entry to pass to the provided
// MediaLog at the requested level.
#if DCHECK_IS_ON()
#define MEDIA_PLOG(level, code, media_log) \
media::LogHelper((media::MediaLogMessageLevel::k##level), (media_log), \
__FILE__, __LINE__, code) \
.stream()
#define MEDIA_LOG(level, media_log) \
media::LogHelper((media::MediaLogMessageLevel::k##level), (media_log), \
__FILE__, __LINE__) \
.stream()
#else
#define MEDIA_LOG(level, media_log) \
media::LogHelper((media::MediaLogMessageLevel::k##level), (media_log), \
nullptr, 0) \
.stream()
#define MEDIA_PLOG(level, code, media_log) \
media::LogHelper((media::MediaLogMessageLevel::k##level), (media_log), \
nullptr, 0, code) \
.stream()
#endif
#if BUILDFLAG(IS_APPLE)
// Prepends a description of an OSStatus to the log entry produced with
// `MEDIA_LOG`.
#define OSSTATUS_MEDIA_LOG(level, status, media_log) \
MEDIA_LOG(level, media_log) \
<< logging::DescriptionFromOSStatus(status) << " (" << (status) << "): "
#endif // BUILDFLAG(IS_APPLE)
// Logs only while |count| < |max|, increments |count| for each log, and warns
// in the log if |count| has just reached |max|.
// Multiple short-circuit evaluations are involved in this macro:
// 1) LAZY_STREAM avoids wasteful MEDIA_LOG and evaluation of subsequent stream
// arguments if |count| is >= |max|, and
// 2) the |condition| given to LAZY_STREAM itself short-circuits to prevent
// incrementing |count| beyond |max|.
// Note that LAZY_STREAM guarantees exactly one evaluation of |condition|, so
// |count| will be incremented at most once each time this macro runs.
// The "|| true" portion of |condition| lets logging occur correctly when
// |count| < |max| and |count|++ is 0.
// TODO(wolenetz,chcunningham): Consider using a helper class instead of a macro
// to improve readability.
#define LIMITED_MEDIA_LOG(level, media_log, count, max) \
LAZY_STREAM(MEDIA_LOG(level, media_log), \
(count) < (max) && ((count)++ || true)) \
<< (((count) == (max)) ? "(Log limit reached. Further similar entries " \
"may be suppressed): " \
: "")
} // namespace media
#endif // MEDIA_BASE_MEDIA_LOG_H_