Ghi log thông báo
Ghi log là cách phổ biến nhất để thông báo cho người dùng về thông tin hiện tại liên quan đến hoạt động của chương trình. Đây có thể là trạng thái hoàn thành bình thường, chỉ báo tiến trình trong quá trình tính toán dài, hoặc dữ liệu gỡ lỗi để tìm và tái hiện lỗi.
Thật không may, không có lập trình viên nào miễn nhiễm với lỗi trong mã của họ. Do đó, các nhà phát triển thường cố gắng để lại cái gọi là "dấu vết vụn bánh mì": ghi log các giai đoạn chính của việc thực thi chương trình (ít nhất là chuỗi các lời gọi hàm).
Chúng ta đã quen thuộc với hai hàm ghi log − Print
và PrintFormat
. Chúng ta đã sử dụng chúng trong các ví dụ ở các phần trước. Chúng ta phải "đưa chúng vào sử dụng" sớm theo cách đơn giản hóa vì hầu như không thể làm việc mà không có chúng.
Một lời gọi hàm thường tạo ra một bản ghi. Tuy nhiên, nếu chuỗi đầu ra chứa ký tự xuống dòng (\n
), thông tin sẽ được chia thành hai phần.
Lưu ý rằng tất cả các lời gọi Print
và PrintFormat
được chuyển thành các mục nhập nhật ký trên tab Experts
của cửa sổ Toolbox
. Mặc dù tab được gọi là Experts
, nó thu thập kết quả của tất cả các lệnh in, bất kể loại chương trình MQL nào.
Nhật ký được lưu trữ trong các tệp được tổ chức theo nguyên tắc "một ngày = một tệp": chúng có tên dạng YYYYMMDD.log (Y là năm, M là tháng, D là ngày). Các tệp nằm trong thư mục <data directory>/MQL5/Logs
(không nhầm lẫn với nhật ký hệ thống terminal trong thư mục <data directory>/Logs
).
Lưu ý
Lưu ý rằng trong quá trình ghi log số lượng lớn (nếu các lời gọi hàm Print
tạo ra một lượng lớn thông tin trong thời gian ngắn), terminal chỉ hiển thị một số mục nhập trong cửa sổ để tối ưu hóa hiệu suất. Ngoài ra, người dùng dù sao cũng không thể xem tất cả các thông báo ngay lập tức. Để xem phiên bản đầy đủ của nhật ký, bạn cần chạy lệnh View
trong menu ngữ cảnh. Kết quả là, một cửa sổ chứa nhật ký sẽ mở ra.
Cũng cần lưu ý rằng thông tin từ nhật ký được lưu vào bộ nhớ đệm khi ghi ra đĩa, tức là được ghi vào tệp theo các khối lớn ở chế độ chậm, do đó tại bất kỳ thời điểm nào, tệp nhật ký thường không chứa các mục nhập mới nhất (mặc dù chúng có thể thấy trong cửa sổ). Để khởi tạo việc xóa bộ nhớ đệm ra đĩa, bạn có thể chạy lệnh View
hoặc Open
trong menu ngữ cảnh của nhật ký.
Mỗi mục nhập nhật ký được đi kèm bởi thời gian chính xác đến mili giây, cũng như tên của chương trình (và đồ họa của nó) đã tạo ra hoặc gây ra thông báo này.
void Print(argument, ...)
Hàm này in một hoặc nhiều giá trị vào nhật ký chuyên gia, trên một dòng (nếu dữ liệu đầu ra không chứa ký tự \n
).
Các đối số có thể thuộc bất kỳ kiểu tích hợp nào. Chúng được phân tách bằng dấu phẩy. Số lượng tham số không thể vượt quá 64. Số lượng tham số biến đổi được biểu thị bằng dấu ba chấm trong nguyên mẫu, nhưng MQL5 không cho phép bạn mô tả các hàm của riêng mình với đặc điểm tương tự: chỉ một số hàm API tích hợp có số lượng tham số biến đổi (đặc biệt là StringFormat
, Print
, PrintFormat
, và Comment
).
Đối với cấu trúc và lớp, bạn nên triển khai phương thức in tích hợp hoặc hiển thị các trường của chúng riêng lẻ.
Ngoài ra, hàm này không thể xử lý mảng. Bạn có thể hiển thị chúng từng phần tử một, hoặc sử dụng hàm ArrayPrint
.
Giá trị kiểu double
được hàm xuất ra với độ chính xác lên đến 16 chữ số có nghĩa (bao gồm cả phần nguyên và phần thập phân). Một số có thể được hiển thị ở định dạng truyền thống hoặc định dạng khoa học (với số mũ), tùy thuộc vào cái nào gọn hơn. Giá trị kiểu float
được hiển thị với độ chính xác 7 chữ số thập phân. Để hiển thị số thực với độ chính xác khác hoặc để chỉ định rõ ràng định dạng, bạn phải sử dụng hàm PrintFormat
.
Giá trị kiểu bool
được xuất dưới dạng chuỗi "true" hoặc "false".
Ngày được hiển thị với ngày và giờ được chỉ định với độ chính xác tối đa (đến giây), ở định dạng "YYYY.MM.DD hh:mm:ss". Để hiển thị ngày ở định dạng khác, sử dụng hàm TimeToString
(xem phần Ngày và giờ).
Giá trị liệt kê được hiển thị dưới dạng số nguyên. Để hiển thị tên phần tử, sử dụng hàm EnumToString
(xem phần Liệt kê).
Ký tự một byte và hai byte cũng được xuất dưới dạng số nguyên. Để hiển thị ký tự dưới dạng ký hiệu hoặc chữ cái, sử dụng các hàm CharToString
hoặc ShortToString
(xem phần Làm việc với ký hiệu và bảng mã).
Giá trị kiểu color
được hiển thị dưới dạng chuỗi với bộ ba số chỉ cường độ của mỗi thành phần màu ("R, G, B") hoặc dưới dạng tên màu nếu màu này có trong tập hợp màu.
Để biết thêm thông tin về việc chuyển đổi giá trị của các kiểu khác nhau thành chuỗi, xem chương Chuyển đổi dữ liệu của các kiểu tích hợp (đặc biệt trong các phần Số sang chuỗi và ngược lại, Ngày và giờ, Màu sắc).
Khi làm việc trong trình kiểm tra chiến lược ở chế độ một lần chạy (kiểm tra Expert Advisor hoặc chỉ báo), kết quả của hàm Print
được xuất ra nhật ký của tác nhân kiểm tra.
Khi làm việc trong trình kiểm tra chiến lược ở chế độ tối ưu hóa, việc ghi log bị triệt tiêu vì lý do hiệu suất, do đó hàm Print
không có hiệu ứng rõ ràng. Tuy nhiên, tất cả các biểu thức được cung cấp dưới dạng đối số vẫn được đánh giá.
Tất cả các đối số, sau khi được chuyển đổi thành biểu diễn chuỗi, được nối thành một chuỗi chung mà không có ký tự phân tách nào. Nếu cần, các ký tự phân tách như vậy phải được ghi rõ ràng trong danh sách đối số. Ví dụ:
int x;
bool y;
datetime z;
...
Print(x, ", ", y, ", ", z);
2
3
4
5
Ở đây, 3 biến được ghi log, phân tách bằng dấu phẩy. Nếu không có các chuỗi ký tự trung gian ", ", giá trị của các biến sẽ bị dính liền trong mục nhật ký.
Có rất nhiều trường hợp áp dụng Print
có thể được tìm thấy từ các phần đầu tiên của cuốn sách (ví dụ: Chương trình đầu tiên, Gán và khởi tạo, biểu thức và mảng, và trong các phần khác).
Là một cách làm việc mới với Print
, chúng ta sẽ triển khai một lớp đơn giản cho phép hiển thị chuỗi giá trị bất kỳ mà không cần chỉ định ký tự phân tách giữa mỗi giá trị lân cận. Chúng ta sử dụng phương pháp nạp chồng toán tử <<
, tương tự như cách được sử dụng trong luồng I/O của C++ (std::cout).
Định nghĩa lớp sẽ được đặt trong một tệp tiêu đề riêng OutputStream.mqh
. Một lớp được hiển thị dưới đây ở dạng đơn giản hóa:
class OutputStream
{
protected:
ushort delimiter;
string line;
// thêm đối số tiếp theo, phân tách bằng ký tự phân cách (nếu có)
void appendWithDelimiter(const string v)
{
line += v;
if(delimiter != 0)
{
line += ShortToString(delimiter);
}
}
public:
OutputStream(ushort d = 0): delimiter(d) { }
template<typename T>
OutputStream *operator<<(const T v)
{
appendWithDelimiter((string)v);
return &this;
}
OutputStream *operator<<(OutputStream &self)
{
if(&this == &self)
{
print(line); // xuất chuỗi đã được tạo
line = NULL;
}
return &this;
}
};
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
Ý nghĩa của nó là tích lũy trong biến chuỗi line
các biểu diễn chuỗi của bất kỳ đối số nào được truyền qua toán tử <<
. Nếu một ký tự phân cách được chỉ định trong hàm tạo lớp, nó sẽ tự động được chèn giữa các đối số. Vì toán tử nạp chồng trả về một con trỏ đến đối tượng, chúng ta có thể truyền liên tiếp một chuỗi đối số:
OutputStream out(',');
out << x << y << z << out;
2
Là một thuộc tính của việc kết thúc thu thập dữ liệu, và để thực sự xuất nội dung line
vào nhật ký, một nạp chồng của cùng toán tử cho chính đối tượng được sử dụng.
Lớp thực tế phức tạp hơn một chút. Đặc biệt, nó cho phép thiết lập không chỉ ký tự phân cách mà còn độ chính xác khi hiển thị số thực, cũng như các cờ để chọn các trường trong giá trị ngày và giờ. Ngoài ra, lớp hỗ trợ in ký tự, ushort
, dưới dạng ký tự (thay vì mã số nguyên), xuất mảng đơn giản hóa (vào một chuỗi riêng), màu ở định dạng thập lục phân dưới dạng giá trị duy nhất (và không phải là bộ ba số phân tách bằng dấu phẩy, vì dấu phẩy thường được dùng làm ký tự phân cách, và khi đó các thành phần màu trong nhật ký trông giống như 3 biến khác nhau).
Một minh họa về việc sử dụng lớp được đưa ra trong script OutputStream.mq5
:
void OnStart()
{
OutputStream os(5, ',');
bool b = true;
datetime dt = TimeCurrent();
color clr = C'127, 128, 129';
int array[] = {100, 0, -100};
os << M_PI << "text" << clrBlue << b << array << dt << clr << '@' << os;
/*
ví dụ đầu ra
3.14159,text,clrBlue,true
[100,0,-100]
2021.09.07 17:38,clr7F8081,@
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void PrintFormat(const string format, ...) ≡ void printf(const string format, ...)
Hàm này ghi log một tập hợp đối số dựa trên chuỗi định dạng được chỉ định. Tham số format
không chỉ cung cấp một mẫu chuỗi xuất văn bản tự do được hiển thị "nguyên trạng", mà còn có thể chứa các chuỗi thoát mô tả cách định dạng các đối số cụ thể.
Tổng số tham số, bao gồm chuỗi định dạng, không thể vượt quá 64. Các hạn chế về kiểu tham số tương tự như hàm print
.
Nguyên tắc làm việc và định dạng của PrintFormat
giống hệt như được mô tả cho hàm StringFormat
(xem phần Đầu ra dữ liệu định dạng phổ quát vào chuỗi). Sự khác biệt duy nhất là StringFormat
trả về chuỗi đã tạo cho mã gọi, còn print format
gửi đến nhật ký. Có thể nói rằng PrintFormat
có tương đương có điều kiện sau:
Print(StringFormat(<list of arguments as is, including format>))
Ngoài tên đầy đủ PrintFormat
, bạn có thể sử dụng bí danh ngắn hơn printf
.
Giống như hàm Print
, PrintFormat
có một số đặc điểm cụ thể khi làm việc trong trình kiểm tra ở chế độ tối ưu hóa: đầu ra của nó vào nhật ký bị triệt tiêu để cải thiện hiệu suất.
Chúng ta đã gặp trong nhiều phần các script sử dụng PrintFormat
, ví dụ: Chuyển tiếp trả về, Màu sắc, Mảng động, Quản lý mô tả tệp, Lấy danh sách biến toàn cục.