1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// This file implements debugPrint in terms of print, so avoiding
6// calling "print" is sort of a non-starter here...
7// ignore_for_file: avoid_print
8
9/// @docImport 'package:flutter/widgets.dart';
10library;
11
12import 'dart:async';
13import 'dart:collection';
14
15/// Signature for [debugPrint] implementations.
16///
17/// If a [wrapWidth] is provided, each line of the [message] is word-wrapped to
18/// that width. (Lines may be separated by newline characters, as in '\n'.)
19///
20/// By default, this function very crudely attempts to throttle the rate at
21/// which messages are sent to avoid data loss on Android. This means that
22/// interleaving calls to this function (directly or indirectly via, e.g.,
23/// [debugDumpRenderTree] or [debugDumpApp]) and to the Dart [print] method can
24/// result in out-of-order messages in the logs.
25///
26/// The implementation of this function can be replaced by setting the
27/// [debugPrint] variable to a new implementation that matches the
28/// [DebugPrintCallback] signature. For example, flutter_test does this.
29///
30/// The default value is [debugPrintThrottled]. For a version that acts
31/// identically but does not throttle, use [debugPrintSynchronously].
32typedef DebugPrintCallback = void Function(String? message, { int? wrapWidth });
33
34/// Prints a message to the console, which you can access using the "flutter"
35/// tool's "logs" command ("flutter logs").
36///
37/// The [debugPrint] function logs to console even in [release mode](https://docs.flutter.dev/testing/build-modes#release).
38/// As per convention, calls to [debugPrint] should be within a debug mode check or an assert:
39/// ```dart
40/// if (kDebugMode) {
41/// debugPrint('A useful message');
42/// }
43/// ```
44///
45/// See also:
46///
47/// * [DebugPrintCallback], for function parameters and usage details.
48/// * [debugPrintThrottled], the default implementation.
49DebugPrintCallback debugPrint = debugPrintThrottled;
50
51/// Alternative implementation of [debugPrint] that does not throttle.
52/// Used by tests.
53void debugPrintSynchronously(String? message, { int? wrapWidth }) {
54 if (message != null && wrapWidth != null) {
55 print(message.split('\n').expand<String>((String line) => debugWordWrap(line, wrapWidth)).join('\n'));
56 } else {
57 print(message);
58 }
59}
60
61/// Implementation of [debugPrint] that throttles messages. This avoids dropping
62/// messages on platforms that rate-limit their logging (for example, Android).
63///
64/// If `wrapWidth` is not null, the message is wrapped using [debugWordWrap].
65void debugPrintThrottled(String? message, { int? wrapWidth }) {
66 final List<String> messageLines = message?.split('\n') ?? <String>['null'];
67 if (wrapWidth != null) {
68 _debugPrintBuffer.addAll(messageLines.expand<String>((String line) => debugWordWrap(line, wrapWidth)));
69 } else {
70 _debugPrintBuffer.addAll(messageLines);
71 }
72 if (!_debugPrintScheduled) {
73 _debugPrintTask();
74 }
75}
76int _debugPrintedCharacters = 0;
77const int _kDebugPrintCapacity = 12 * 1024;
78const Duration _kDebugPrintPauseTime = Duration(seconds: 1);
79final Queue<String> _debugPrintBuffer = Queue<String>();
80final Stopwatch _debugPrintStopwatch = Stopwatch(); // flutter_ignore: stopwatch (see analyze.dart)
81// Ignore context: This is not used in tests, only for throttled logging.
82Completer<void>? _debugPrintCompleter;
83bool _debugPrintScheduled = false;
84void _debugPrintTask() {
85 _debugPrintScheduled = false;
86 if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) {
87 _debugPrintStopwatch.stop();
88 _debugPrintStopwatch.reset();
89 _debugPrintedCharacters = 0;
90 }
91 while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.isNotEmpty) {
92 final String line = _debugPrintBuffer.removeFirst();
93 _debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead
94 print(line);
95 }
96 if (_debugPrintBuffer.isNotEmpty) {
97 _debugPrintScheduled = true;
98 _debugPrintedCharacters = 0;
99 Timer(_kDebugPrintPauseTime, _debugPrintTask);
100 _debugPrintCompleter ??= Completer<void>();
101 } else {
102 _debugPrintStopwatch.start();
103 _debugPrintCompleter?.complete();
104 _debugPrintCompleter = null;
105 }
106}
107
108/// A Future that resolves when there is no longer any buffered content being
109/// printed by [debugPrintThrottled] (which is the default implementation for
110/// [debugPrint], which is used to report errors to the console).
111Future<void> get debugPrintDone => _debugPrintCompleter?.future ?? Future<void>.value();
112
113final RegExp _indentPattern = RegExp('^ *(?:[-+*] |[0-9]+[.):] )?');
114enum _WordWrapParseMode { inSpace, inWord, atBreak }
115
116/// Wraps the given string at the given width.
117///
118/// The `message` should not contain newlines (`\n`, U+000A). Strings that may
119/// contain newlines should be [String.split] before being wrapped.
120///
121/// Wrapping occurs at space characters (U+0020). Lines that start with an
122/// octothorpe ("#", U+0023) are not wrapped (so for example, Dart stack traces
123/// won't be wrapped).
124///
125/// Subsequent lines attempt to duplicate the indentation of the first line, for
126/// example if the first line starts with multiple spaces. In addition, if a
127/// `wrapIndent` argument is provided, each line after the first is prefixed by
128/// that string.
129///
130/// This is not suitable for use with arbitrary Unicode text. For example, it
131/// doesn't implement UAX #14, can't handle ideographic text, doesn't hyphenate,
132/// and so forth. It is only intended for formatting error messages.
133///
134/// The default [debugPrint] implementation uses this for its line wrapping.
135Iterable<String> debugWordWrap(String message, int width, { String wrapIndent = '' }) {
136 if (message.length < width || message.trimLeft()[0] == '#') {
137 return <String>[message];
138 }
139 final List<String> wrapped = <String>[];
140 final Match prefixMatch = _indentPattern.matchAsPrefix(message)!;
141 final String prefix = wrapIndent + ' ' * prefixMatch.group(0)!.length;
142 int start = 0;
143 int startForLengthCalculations = 0;
144 bool addPrefix = false;
145 int index = prefix.length;
146 _WordWrapParseMode mode = _WordWrapParseMode.inSpace;
147 late int lastWordStart;
148 int? lastWordEnd;
149 while (true) {
150 switch (mode) {
151 case _WordWrapParseMode.inSpace: // at start of break point (or start of line); can't break until next break
152 while ((index < message.length) && (message[index] == ' ')) {
153 index += 1;
154 }
155 lastWordStart = index;
156 mode = _WordWrapParseMode.inWord;
157 case _WordWrapParseMode.inWord: // looking for a good break point
158 while ((index < message.length) && (message[index] != ' ')) {
159 index += 1;
160 }
161 mode = _WordWrapParseMode.atBreak;
162 case _WordWrapParseMode.atBreak: // at start of break point
163 if ((index - startForLengthCalculations > width) || (index == message.length)) {
164 // we are over the width line, so break
165 if ((index - startForLengthCalculations <= width) || (lastWordEnd == null)) {
166 // we should use this point, because either it doesn't actually go over the
167 // end (last line), or it does, but there was no earlier break point
168 lastWordEnd = index;
169 }
170 if (addPrefix) {
171 wrapped.add(prefix + message.substring(start, lastWordEnd));
172 } else {
173 wrapped.add(message.substring(start, lastWordEnd));
174 addPrefix = true;
175 }
176 if (lastWordEnd >= message.length) {
177 return wrapped;
178 }
179 // just yielded a line
180 if (lastWordEnd == index) {
181 // we broke at current position
182 // eat all the spaces, then set our start point
183 while ((index < message.length) && (message[index] == ' ')) {
184 index += 1;
185 }
186 start = index;
187 mode = _WordWrapParseMode.inWord;
188 } else {
189 // we broke at the previous break point, and we're at the start of a new one
190 assert(lastWordStart > lastWordEnd);
191 start = lastWordStart;
192 mode = _WordWrapParseMode.atBreak;
193 }
194 startForLengthCalculations = start - prefix.length;
195 assert(addPrefix);
196 lastWordEnd = null;
197 } else {
198 // save this break point, we're not yet over the line width
199 lastWordEnd = index;
200 // skip to the end of this break point
201 mode = _WordWrapParseMode.inSpace;
202 }
203 }
204 }
205}
206