You have been redirected from an outdated version of the article. Below is the content available on this topic. To view the old article click here.

Log

Flow Elements - Log

Description

The Log element allows to write to the Structr log. It will additionally prefix the given message content with the current flow name and the log element’s UUID.

Parameters

Name Description
Prev Accepts another element’s Next socket
Next Connects to another element’s Prev socket
DataSource Accepts another element’s DataSource
Script Given script will be executed as StructrScript with data in it’s context.

Search results for "Log"

changelog()

Returns the changelog for a specific entity. See Changelog for details on what the contents of a changelog are.

The resolve parameter controls if remote entities are resolved. Every changelog entry which has a target will be resolved as targetObj (if the remote entity still exists in the database).

Filtering (supported from version 2.2)
All filter options are chained using the boolean AND operator. Only changelog entries matching all of the specified filters will be returned.
For filter keys which can occurr more than once, the filter values are combined using the boolean OR operator (see examples 1 and 2)

Filter Key Applicable Changlog verbs (*) Changelog Entry will be returned if max. occurrences
timeFrom (**) create, delete, link, unlink, change timeFrom <= time of the entry 1 (***)
timeTo (**) create, delete, link, unlink, change timeTo >= time of the entry 1 (***)
verb create, delete, link, unlink, change verb of the entry matches at least one of the verbs n (****)
userId create, delete, link, unlink, change userId of the entry matches at least one of the userIds n (****)
userName create, delete, link, unlink, change userName of the entry matches at least one of the userNames n (****)
relType link, unlink rel of the entry matches at least one of the relTypes n (****)
relDir link, unlink relDir of the entry matches the given relDir 1 (***)
target create, delete, link, unlink target of the entry matches at least one of the targets n (****)
key change key of the entry matches at least one of the keys n (****)

(*) If a filter parameter is supplied, only changelog entries can be returned to which it is applicable. (e.g. combining key and relType can never yield a result as they are mutually exclusive)
(**) timeFrom/timeTo can be specified as a Long (time in ms since epoch), as a JavaScript Date object, or as a String with the format yyyy-MM-dd'T'HH:mm:ssZ
(***) The last supplied parameter takes precedence over the others
(****) The way we supply multiple occurrences of a keyword can differ from StructrScript to JavaScript

changelog(entity [, resolve=false [, filterKey1, filterValue2 [ , ... ] ] ] )
$.changelog(entity [, resolve=false [, map]])

log()

Prints a string representation of the given objects to the Structr log file.

log(objects...)

log_event()

Creates an entity of type LogEvent with the current timestamp and the given values.
All four parameters (action, message, subject and object) can be arbitrary strings.

In JavaScript, the function can be called with a single map as parameter. The event paramters are taken from that map.

log_event(action, message [, subject [, object ]])
$.logEvent(map)

serverlog()

Returns the last n lines from the server log file

serverlog([lines = 50])

user_changelog()

Returns the changelog for the changes a specific user made.

user_changelog(user [, resolve=false [, filterKey1, filterValue2 [ , ... ] ] ] )
$.user_changelog(user [, resolve=false [, map]])

Logging

nodeextender.log Enables the logging of generated Java code of the dynamic schema entities to the logfile. This setting allows you to investigate and debug the generation of Java code for your schema entities in cases where something goes wrong.
nodeextender.log.errors Enables the logging of Java compilation errors when compiling the dynamic schema of your application.
log.requests Enables full request logging for all requests handled by Structr. Caution, the log file can get very large when a lot of requests with lots of content are made.
log.debug Controls the debug() built-in function, which will behave exactly like the log() function if enabled.
log.functions.stacktrace Enables stacktrace logging for script calls and built-in functions. If enabled, a scripting error will write the full Java stack trace to the logfile.
log.prefix Sets the prefix for the request log file that is written when log.requests is enabled.
log.javascript.exception.request Adds path, queryString and parameterMap to JavaScript exceptions (if available) if enabled.
log.directorywatchservice.scanquietly Prevents logging of each scan process for every folder processed by the directory watch service.

Example Implementation

// get all required elements
const loginForm = document.getElementById('login-form');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const loginButton = document.getElementById('login-button');

loginForm.addEventListener('submit', async (event) => {

event.preventDefault();

loginButton.disabled = true;

// get from values
const email = emailInput.value;
const password = passwordInput.value;

// login
const response = await fetch('/structr/rest/login', {
method: 'POST',
body: JSON.stringify({
eMail: email,
password: password
})
});

if (response.status === 202) {

let redirectUrl = response.headers.get('twoFactorLoginPage')
+ '?token=' + response.headers.get('token')
+ '&qrdata=' + (response.headers.get('qrdata') ?? '');

window.location.href = redirectUrl;

} else {

if (response.ok) {

loginButton.textContent = 'Login successful';
window.location.href = '/';

} else {

loginButton.disabled = false;
loginButton.textContent = 'Wrong username or password.';

window.setTimeout(function() {
loginButton.value = 'Login';
loginButton.disabled = false;
}, 2000);
}
}
});
<div id="qrimage-wrapper">
<img id="qrimage" style="margin-left: calc(50% - 100px);">
<div>To use two factor authentication, scan this QR code with an authenticator app on your smartphone.</div>

<div class="text-sm mt-6">
<div>
<b>Android: </b>
<a class="cursor-pointer hover:text-blue-400 text-blue-700" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en&gl=US">Google Authenticator</a> on Google Playstore
</div>
<div>
<b>Apple iOS: </b>
<a class="cursor-pointer hover:text-blue-400 text-blue-700" href="https://apps.apple.com/us/app/google-authenticator/id388497605">Google Authenticator</a> on App Store
</div>
</div>
</div>

<form action="#" id="twoFactorForm">
<input id="twoFactorToken" type="hidden" value="${request.token}">
<div class="my-6">
<label class="block text-sm font-medium leading-5 text-gray-700">Two Factor Code</label>
<input id="twoFactorCode" class="appearance-none block w-full px-3 py-2 bg-blue-100 border border-gray-300 rounded-md placeholder-gray-400 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 transition duration-150 ease-in-out sm:text-sm sm:leading-5">
</div>
<button type="submit" id="login-button" class="w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-500 focus:outline-none focus:border-blue-700 focus:shadow-outline-indigo active:bg-blue-700 transition duration-150 ease-in-out">Login</button>
</form>

<script>
document.addEventListener('DOMContentLoaded', () => {

// get all required elements
const qrimageWrapper = document.getElementById('qrimage-wrapper');
const token = document.getElementById('twoFactorToken').value;
const codeInput = document.getElementById('twoFactorCode').value;
const loginButton = document.getElementById('login-button');

let qrdata = (new URLSearchParams(location.search)).get('qrdata');

if (!qrdata) {
// remove qr code placeholder if user is not shown qr code
qrimageWrapper.remove();
} else {
// transform url-safe qr code to regular base64 to display as image
qrimageWrapper.querySelector('#qrimage').src = 'data:image/png;base64, ' + qrdata.replaceAll('_', '/').replaceAll('-', '+');
}

document.getElementById('twoFactorForm').addEventListener('submit', async (event) => {

event.preventDefault();

loginButton.disabled = true;

const response = await fetch('/structr/rest/login', {
method: 'POST',
body: JSON.stringify({
twoFactorToken: token,
twoFactorCode: codeInput
})
});

if (response.ok) {

loginButton.textContent = 'Login successful';
window.location.href = '/';

} else {

let buttonText = 'Login failed - is device time correct?';

let reason = response.headers.get('reason');

if (reason === 'wrongTwoFactorCode') {
buttonText = 'Two Factor Code is not correct';
}

loginButton.disabled = false;
loginButton.textContent = buttonText;

window.setTimeout(function() {
loginButton.textContent = 'Login';
loginButton.disabled = false;
}, 2000);
}
});
});
</script>

Changelog

The changelog itself can be retrieved using the builtin changelog() function. The changelog is written to disk at changelog.path with the subfolders n for nodes and r for relationships. If user-centric changelog (application.changelog.user_centric.enabled) is enabled the changelog is written to the subfolder u. In older versions of structr the changelog was written to the structrChangeLog property of a node. The following tables show the possible keys/values of modification records. Modification records are stored as a JSON string. (see the example below)

Example Implementation

Step 1: Create the basic login page/form as per usual. A regular HTML <form> with custom submission handling to log in.

If the user is not configured for two-factor authentication, they are logged in.

If the status is 202 is returned, two-factor authentication is required. The returned headers from the login POST contain the required data to use on the next page (and are passed as request parameters in the example code):

twoFactorLoginPage The page configured in security.twofactorauthentication.loginpage
token The token required to log in
qrdata The URL-safe base64 encoded data of the QR code which can be scanned with an authenticator app

Java-Logging

Structr is written in Java, and we use the Log4J logging framework (the API) and the Logback implementation of that API. The Logback configuration lives in the classpath, in a file called logback.xml. Structr contains a logback.xml file that specifies settings for the console appender and the file appender which writes to /var/log/structr.conf.

Login/Logout Behaviour

callbacks.logout.onsave When set to true a user logout will trigger the onSave method on the user. Disabled by default as the global handler onStructrLogout can be used to achieve similar results.
callbacks.login.onsave When set to true a user login will trigger the onSave method on the user. Disabled by default as the global handler onStructrLogin can be used to achieve similar results. Triggers on failed login attempts and on each step of the two-factor authentication process.

Unable to log in - Too many failed login attempts

By default, Structr auto-locks an account after 4 incorrect login attempts. Further login attempts (even with the correct password) result in the error message “Too many failed login attempts”. This security setting can be configured via the configuration setting security.passwordpolicy.maxfailedattempts. The default is 4 and the functionality is disabled for any number less than or equal to 0.

The number of failed login attempts is stored in the user node in the attribute passwordAttempts. Setting this attribute to 0 enables the user to log in again.

This can be done using the superadmin credentials (or any other admin account) by sending a PUT request to the appropriate resource (/structr/rest/User/[UUID_OF_USER]) with the body {"passwordAttempts":0}. It can also be solved by temporarily changing the setting to 0 and logging in.

Sessions

To use session-based authentication, you log in the user with a special request to the login endpoint at /structr/rest/login that returns a session cookie which you can use to authenticate subsequent requests. Since the login endpoint is a REST endpoint, you must create a Resource Access Grant with the special signature _login that allows the POST method for non-authenticated users.

Logout

To log out of a session, you can send a POST request containing the session cookie to the logout endpoint at /structr/rest/logout. This endpoint needs a Resource Access Grant as well, so in order to use the logout endpoint, you must create a Resource Access Grant with the special signature _logout that allows POST for authenticated users.

Debugging

log.cypher.debug Prints all generated cypher queries to the server log. if enabled.
log.cypher.debug.ping If enabled, queries generated for the Websocket PING request will be logged. Can only be used in conjunction with log.cypher.debug.

Changelog

application.changelog.enabled If enabled, all changes to nodes and relationships will be written to a changelog.
application.changelog.user_centric.enabled Enables a special kind of changelog, which logs user activities.

GNU/Linux Debian Package (DEB)

The Structr logfile will be written to /var/log/structr.log. The following screenshot shows the contents of a typical structr.log right after the start.