Problem using diary with standalone application

I am developing a MATLAB app using the appdesigner (MATLAB version R2019b), which is intented to be used as standalone '.exe' on Windows. For traceablity I wanted to include a log-file of all outputs. The 'diary()' function seemed to be a good function for this. In my code I have a lot of 'disp(...)' commands (also in code parts that I cannot or do not want to change). The output for these disp-commands works perfectly in the MATLAB command window when I run the application within the MATLAB environment. However, when run it as Windows standalone the behavior is not logical to me: In the WIndows cmd window the output is the same as in the MATLAB command window, but the diary file only logs the output for the 'startupFcn()' and the 'UIFigureCloseRequest()' functions and not for any callback, e.g. here from a button 'DispHelloWorldButtonPushed()'.
Does anybody know how to solve this issue and where it comes from? Btw, also the build-in 'Create log file' option during compilation shows the same behavior as the 'diary' function.
Here is a minimal working example of an app with this behavior:
classdef Test < matlab.apps.AppBase
% Properties that correspond to app components
properties (Access = public)
UIFigure matlab.ui.Figure
DispHelloWorldButton matlab.ui.control.Button
end
% Callbacks that handle component events
methods (Access = private)
% Code that executes after component creation
function startupFcn(app)
diary(fullfile(pwd,'log.txt'))
diary on
disp('Hello World!')
end
% Button pushed function: DispHelloWorldButton
function DispHelloWorldButtonPushed(app, event)
diary on
disp('Button: Hello World!')
end
% Close request function: UIFigure
function UIFigureCloseRequest(app, event)
disp('Bye bye World!')
diary off
delete(app)
end
end
% Component initialization
methods (Access = private)
% Create UIFigure and components
function createComponents(app)
% Create UIFigure and hide until all components are created
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100 100 640 480];
app.UIFigure.Name = 'UI Figure';
app.UIFigure.CloseRequestFcn = createCallbackFcn(app, @UIFigureCloseRequest, true);
% Create DispHelloWorldButton
app.DispHelloWorldButton = uibutton(app.UIFigure, 'push');
app.DispHelloWorldButton.ButtonPushedFcn = createCallbackFcn(app, @DispHelloWorldButtonPushed, true);
app.DispHelloWorldButton.Position = [159 161 311 175];
app.DispHelloWorldButton.Text = 'Disp(''Hello World!'')';
% Show the figure after all components are created
app.UIFigure.Visible = 'on';
end
end
% App creation and deletion
methods (Access = public)
% Construct app
function app = Test
% Create UIFigure and components
createComponents(app)
% Register the app with App Designer
registerApp(app, app.UIFigure)
% Execute the startup function
runStartupFcn(app, @startupFcn)
if nargout == 0
clear app
end
end
% Code that executes before app deletion
function delete(app)
% Delete UIFigure when app is deleted
delete(app.UIFigure)
end
end
end

回答 (1 件)

Garmit Pant
Garmit Pant 2024 年 8 月 5 日

1 投票

Hello Jonathan
The issue you're experiencing is due to the way MATLAB handles the standard output and the diary functionality when running as a standalone executable.
The ”diary” function logs the MATLAB Command Window to a text file. Since the standalone application is not running in the MATLAB environment, the Command Window is not in use and thus the “diary” function doesn’t work.
You can use the “fprintf” function to log the outputs to a log-file. You can add a private function to the app using 'Function' button in the ‘Editor’ tab. The following code snippet demonstrates how to write the custom function to log messages and use it:
classdef Test < matlab.apps.AppBase
% Properties that correspond to app components
properties (Access = public)
UIFigure matlab.ui.Figure
DispHelloWorldButton matlab.ui.control.Button
LogFileID % File ID for the log file
end
% Callbacks that handle component events
methods (Access = private)
% Custom logging function
function logMessage(app, message)
if ~isempty(app.LogFileID)
fprintf(app.LogFileID, '%s\n', message);
end
fprintf('%s\n', message); % Also print to command window
end
% Code that executes after component creation
function startupFcn(app)
app.LogFileID = fopen(fullfile(<insert path to file>,'a');
app.logMessage('Hello World!');
end
% Button pushed function: DispHelloWorldButton
function DispHelloWorldButtonPushed(app, event)
app.logMessage('Button: Hello World!');
end
% Close request function: UIFigure
function UIFigureCloseRequest(app, event)
app.logMessage('Bye bye World!');
if ~isempty(app.LogFileID)
fclose(app.LogFileID);
end
delete(app)
end
end
% Component initialization
methods (Access = private)
% Create UIFigure and components
function createComponents(app)
% Create UIFigure and hide until all components are created
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100 100 640 480];
app.UIFigure.Name = 'UI Figure';
app.UIFigure.CloseRequestFcn = createCallbackFcn(app, @UIFigureCloseRequest, true);
% Create DispHelloWorldButton
app.DispHelloWorldButton = uibutton(app.UIFigure, 'push');
app.DispHelloWorldButton.ButtonPushedFcn = createCallbackFcn(app, @DispHelloWorldButtonPushed, true);
app.DispHelloWorldButton.Position = [159 161 311 175];
app.DispHelloWorldButton.Text = 'Disp(''Hello World!'')';
% Show the figure after all components are created
app.UIFigure.Visible = 'on';
end
end
% App creation and deletion
methods (Access = public)
% Construct app
function app = Test
% Create UIFigure and components
createComponents(app)
% Register the app with App Designer
registerApp(app, app.UIFigure)
% Execute the startup function
runStartupFcn(app, @startupFcn)
if nargout == 0
clear app
end
end
% Code that executes before app deletion
function delete(app)
% Delete UIFigure when app is deleted
delete(app.UIFigure)
end
end
end
I hope you find the above explanation and suggestions useful!

5 件のコメント

Jonathan Berthold
Jonathan Berthold 2024 年 8 月 5 日
編集済み: Jonathan Berthold 2024 年 8 月 5 日
I appreciate your suggestion. However, this is not an option for me because within my callbacks (e.g. button) I am also calling functions (containing disp) that I do not want to edit and that are also not connected directly to the app (so I cannot call app.logMessage). The only possiblity for me would be to use the disp() command, which already exists within these functions.
I was more wondering what is different for the disp command used during startupFcn/UIFigureCloseRequest compared to the other app callbacks when used as standalone with no MATLAB command window existent. And can one eliminated the difference between these callbacks so the diary can still be used with disp even without a MATLAB command window?
Garmit Pant
Garmit Pant 2024 年 8 月 6 日
編集済み: Garmit Pant 2024 年 8 月 6 日
Hi Jonathan
I investigated a little further and there might be an issue with the usage of "diary" function in the code snippet that you have shared. If "filename" input argument to the "diary" function does not include a full path, MATLAB redetermines the path of the file relative to the current folder every time logging is enabled. If the current folder has changed since the last time logging was enabled, MATLAB might save the log to a different file. The following code snippet will help you log your callbacks using "disp" and "diary":
classdef Test < matlab.apps.AppBase
% Properties that correspond to app components
properties (Access = public)
UIFigure matlab.ui.Figure
DispHelloWorldButton matlab.ui.control.Button
end
% Callbacks that handle component events
methods (Access = private)
% Code that executes after component creation
function startupFcn(app)
diary(fullfile(<file path>))
disp('Hello World! Latest')
end
% Button pushed function: DispHelloWorldButton
function DispHelloWorldButtonPushed(app, event)
disp('Button: Hello World!')
end
% Close request function: UIFigure
function UIFigureCloseRequest(app, event)
disp('Bye bye World! Latest')
diary off
delete(app)
end
end
% Component initialization
methods (Access = private)
% Create UIFigure and components
function createComponents(app)
% Create UIFigure and hide until all components are created
app.UIFigure = uifigure('Visible', 'off');
app.UIFigure.Position = [100 100 640 480];
app.UIFigure.Name = 'UI Figure';
app.UIFigure.CloseRequestFcn = createCallbackFcn(app, @UIFigureCloseRequest, true);
% Create DispHelloWorldButton
app.DispHelloWorldButton = uibutton(app.UIFigure, 'push');
app.DispHelloWorldButton.ButtonPushedFcn = createCallbackFcn(app, @DispHelloWorldButtonPushed, true);
app.DispHelloWorldButton.Position = [159 161 311 175];
app.DispHelloWorldButton.Text = 'Disp(''Hello World!'')';
% Show the figure after all components are created
app.UIFigure.Visible = 'on';
end
end
% App creation and deletion
methods (Access = public)
% Construct app
function app = Test
% Create UIFigure and components
createComponents(app)
% Register the app with App Designer
registerApp(app, app.UIFigure)
% Execute the startup function
runStartupFcn(app, @startupFcn)
if nargout == 0
clear app
end
end
% Code that executes before app deletion
function delete(app)
% Delete UIFigure when app is deleted
delete(app.UIFigure)
end
end
end
Jonathan Berthold
Jonathan Berthold 2024 年 8 月 6 日
Hello Garmit, that was also one of my first thoughts. Unfortunally, it does not make any difference. As you can see in my code snippet, I also used an absolute path already using the function 'pwd', e.g., "fullfile(pwd, 'log.txt'). Writing the path out explicitely does not change anything, sadly. Also adding an additional cd(<path to diray file>) in the button callback did not help. Thanks for your support, though!
Garmit Pant
Garmit Pant 2024 年 8 月 6 日
Hi Jonathan
Did you try changing how you are handling toggling "diary" to 'on' and 'off'? I have made some changes to that in the code snippet above. Specifically, I have removed the "diary on" statements in your code. The following statement also switches on logging:
diary(fullfile(<file path>))
GIven that you are toggling logging on multiple times using the "diary on" statement, the path of the log file could be changing. Please try to incorporate the code snippet in the comments since I was able to successfully log to a file using the "diary" function that way.
Jonathan Berthold
Jonathan Berthold 2024 年 8 月 6 日
Hi Garmit,
I now found that the problem is somewhat deeper than I expected. I now tried the standalone app on a different machine and the diary worked in all cases! So what I can tell is that it works on MS Windows 10, Version 1809 but not on a MS Windows Server 2016, Version 1607 (which I use for development & testing). Maybe that helps you in finding the error with the different handling of the callbacks. Let me know what you think.

サインインしてコメントする。

カテゴリ

ヘルプ センター および File ExchangeDevelop Apps Using App Designer についてさらに検索

製品

リリース

R2019b

質問済み:

2024 年 8 月 5 日

コメント済み:

2024 年 8 月 6 日

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by