Logs, INI cache and expiry timestamps per site (multi-site hosting)
Read this page if you host several sites on one Exponential installation, each with a VarDir of its own, and want the logs of each site in its own directory, the cache of a site on another file system (memory) or the INI cache of each site cleared on its own. It describes the settings, how they behave under Velocity and on the command line, and what to do when an installation moves from a setup of its own to them.
Warning
.htaccess slows down every request. When .htaccess files are enabled (AllowOverride anything but None), let alone
used, Apache looks for them and reads and evaluates them on every request, in every directory of the path, and
page loading times go up drastically (measured). The only real fix is to switch the feature off for
the virtual host: AllowOverride None in the HTTPS virtual host, and the rules in the virtual host's own
configuration as mod_rewrite rules. That is far faster in production speed tests and well worth the time it takes
to optimize a production configuration. Switch over right away: copy the rules of the
example HTTPS virtual host into your virtual host (with the static cache, also
staticcache_offload.conf), set AllowOverride None, run apachectl configtest, reload Apache and
test the site. The same rules for other servers: nginx, LiteSpeed,
Caddy, IIS, Velocity (built in); where virtual host rules go in a control
panel: Plesk, cPanel, DirectAdmin, ISPConfig.
On LiteSpeed or OpenLiteSpeed, the server's own page cache can also answer anonymous visitors and crawlers without PHP: Page caching with LiteSpeed and OpenLiteSpeed.
In short
| Logs per site | site.ini [FileSettings] UseGlobalLogDir=disabled: the debug logs (error.log, warning.log, ...) and the default logs of eZLog go to the log directory of the site instead of var/log. |
| Logs on another file system | LogDir may be an absolute path, as CacheDir; LogVarDir=var_log puts the logs of every site in var_log/<site>/log with one line. |
| Cache in memory | CacheVarDir=var_cache puts the caches of every site in var_cache/<site>/cache, one memory file system for all of them; or mount one at var/<site>/cache. ExpiryDir keeps expiry.php where the storage is, so a restart does not lose the expiry timestamps. |
| INI cache per site | INICacheDir=site: the INI files of the site are cached in its cache directory, and clearing them leaves the other sites alone. |
| Default | Every setting keeps the behaviour of before. |
| Velocity, scripts | Every value is derived again from site.ini when the siteaccess changes; nothing is kept from one request or one site to the next. |
| Who must act | Nobody, unless [FileSettings] AllowedDeletionDirs relied on an entry that does not exist or on a prefix of a directory name (see what is cleared where). |
Settings
All in site.ini [FileSettings], for each siteaccess of the site (or in an override that applies to all of them, see
per-site settings inside extensions):
| Key | Default | Meaning |
|---|---|---|
LogDir | log | The log directory, inside VarDir; an absolute path is used as it is. storage.log and the debug bar's log go there in any case. |
UseGlobalLogDir | enabled | disabled writes the debug logs and the logs eZLog::write() writes to its default directory into the log directory of the site. |
LogVarDir | empty | A tree for the logs of every site: the first directory of VarDir is replaced, var/example → var_log/example, so the log directory is var_log/example/log. A relative or an absolute path. |
CacheVarDir | empty | The same for the caches: var_cache/example/cache. A directory inside the installation, so that its public caches can be served; an absolute path is used, but its public caches are not served, as with an absolute CacheDir. |
ExpiryDir | empty | Where expiry.php is kept: empty in the cache directory, otherwise a directory inside VarDir or an absolute path. |
INICacheDir | global | site caches the INI files of the site in <cache directory>/ini/; global in var/cache/ini/. |
Many sites with their logs and caches in trees of their own
The setup of an installation with dozens of sites, each in a site extension with VarDir=var/<site>: one line each
in settings/override/site.ini.append.php for all of them.
[FileSettings]
UseGlobalLogDir=disabled
LogVarDir=var_log
CacheVarDir=var_cache
ExpiryDir=expiry
INICacheDir=site# /etc/fstab: the caches of all sites in memory
tmpfs /srv/www/exponential/var_cache tmpfs size=8g,mode=0775,uid=www-data,gid=www-data 0 0- The logs of
var/exampleare invar_log/example/log, its cache invar_cache/example/cache(with the INI cache inini/),expiry.phpinvar/example/expiry/. The storage stays invar/example/storage. - Velocity serves the public caches below
var_cache/(packed scripts and style sheets, text to image) and nothing else there:expVelocity::staticPaths()adds them to the paths of all three engines whenCacheVarDiris set. For Apache, enable the commentedvar_cacherule of.htaccess_rootin your copy,.htaccess(copied with./console exp:setup:htaccess --apply; after an upgrade that command shows the rule among the differences and copies only with--force, so enable it again afterwards); for nginx the commentedlocationin serving the site. See Setting up .htaccess and config.php from the console. - With a cluster file handler, see cluster below.
- Set the settings where every siteaccess of every site reads them (the global override). Velocity writes its list of
served paths when it starts, without a siteaccess: it reads
CacheVarDirfrom the global settings and from the full settings of every siteaccess in[SiteAccessSettings] AvailableSiteAccessList, so aCacheVarDirset for some sites only is served as well. After changing it, restart Velocity.
A site with its cache in memory
# settings/siteaccess/example/site.ini.append.php (and the other siteaccesses of the site)
[FileSettings]
VarDir=var/example
UseGlobalLogDir=disabled
LogDir=/srv/logs/example
ExpiryDir=expiry
INICacheDir=site# /etc/fstab: the cache of the site in memory, emptied at every restart
tmpfs /srv/www/exponential/var/example/cache tmpfs size=2g,mode=0775,uid=www-data,gid=www-data 0 0- The cache directory stays
var/example/cache, so the files the web server and Velocity serve from it (packed scripts and style sheets incache/public,cache/texttoimage) keep their addresses. An absoluteCacheDirwould move them out of the paths that are served, which is why site.ini calls it unsupported. expiry.phpholds the times at which caches and image aliases were last cleared. The image aliases are in the storage, which survives a restart; ifexpiry.phpwere emptied with the cache, image aliases made before a clear would count as current again. WithExpiryDir=expiryit is invar/example/expiry/.- The logs are written to
/srv/logs/example/from the moment the siteaccess is known. Messages written before that (while the request is matched to a siteaccess) still go tovar/log.
What is cleared where
The cache commands and Setup > Caches work as before; the settings only decide which directory a cache item names.
| Cache | What is removed |
|---|---|
INI cache (ini) | <cache directory of the site>/ini/: with INICacheDir=site, the INI cache of this site only |
Global INI cache (global_ini) | var/cache/ini/: the INI files read before a siteaccess is known, and those of sites with INICacheDir=global |
| Caches cleared by timestamp (view cache, template blocks, user info, ...) | A timestamp in the expiry.php of the site (in ExpiryDir when set); purging removes the expired files from the cache directory |
| Clear all, purge all | Everything in the cache directory of the site and var/cache/ini/; expiry.php in ExpiryDir stays and gets the new timestamps |
Checked on an installation with bin/php/ezcache.php -s <siteaccess> for global_ini, ini, the tag content and
all caches, each cleared and purged: the INI cache of the site was removed only by ini and the full clears, the
global one only by global_ini and the full clears, expiry.php in ExpiryDir was kept by every one of them and
received the timestamps, and the expiry.php in the cache directory was not touched.
A running Velocity holds settings in memory as well: after clearing an INI cache, restart it, as Setup > Caches says.
Directories outside the installation: eZDir::recursiveDelete(), which every cache clear goes through, deletes only
inside the root and inside [FileSettings] AllowedDeletionDirs. It also deletes inside the directories the current
site writes to, however they are set (an absolute CacheDir, LogDir or ExpiryDir, a CacheVarDir or
LogVarDir outside the root, a CacheVarDir that is a link to a memory file system, a
$GLOBALS['eZINI_CONFIG_CACHE_DIR'] set by a wrapper): their contents, and the cache directory as a whole, as
inside the root; never the log or expiry directory itself, nothing beside them, never a file system root, and never
the installation or a directory that contains it (a LogDir of /var/www allows nothing). An AllowedDeletionDirs
entry that does not exist allows nothing (before, it allowed every path), and /srv/cache no longer allows
/srv/cache-other.
Velocity, scripts and siteaccess changes
eZSiteAccess::change() sets all three for the siteaccess it changes to: the log directory
(eZSiteAccess::updateLogDirectory()), the INI cache directory (eZSiteAccess::updateINICacheDirectory()) and the
expiry file (eZExpiryHandler::resetForCurrentCacheDirectory()). It does so for every front controller, including
index_soap.php and index_webdav.php, and for every script, so bin/php/ezcache.php -s example and the cronjobs of a site
write into the directories of that site.
With INICacheDir=site the site.ini of the siteaccess itself is cached in the INI cache of the site as well:
change() reads it from the shared var/cache/ini/ first (that is where it learns INICacheDir) and once more from
the directory of the site, so clearing the INI cache of the site refreshes it. That second read costs one cached file
per request for such a site. A site changed to afterwards in the same process never leaves a copy of its site.ini
in the INI cache of the site before.
- Velocity serves the sites of an installation one request after another in the same worker. Between requests it
puts back the globals and static properties of its warm-up and removes every other global, except the type
registries of its Exponential preset. The three paths live in globals (
eZDebugLogDir,eZINI_CONFIG_CACHE_DIR, the instances ofeZDebugandeZExpiryHandler) and in no static property, so the next request starts with the defaults and takes the paths of its own site when its siteaccess is known. - The Velocity warm-up renders a site in the parent process before the workers are forked. It puts the log and
INI cache directories that site set back to those of a request without a siteaccess
(
eZSiteAccess::resetSitePaths()) before it removes the globals of the render, so no request starts with the directories of the site the warm-up rendered. - Two sites in one process (a script that changes siteaccess, or the content cache clearing that visits every
siteaccess): the second change takes the paths of the second site; a site without the settings goes back to
var/log,var/cache/ini/and its cache directory. Changing to the same site twice changes nothing. - A directory the installation set itself: a multi-site wrapper may set
$GLOBALS['eZINI_CONFIG_CACHE_DIR']before startup, for one INI cache shared by all projects. A site withINICacheDir=siteuses its own; after it, a site without the setting gets the wrapper's directory back.
Checked with Velocity 0.0.4.45 (engine qbix, four persistent workers) on an installation with two siteaccesses, one
with UseGlobalLogDir=disabled, INICacheDir=site and ExpiryDir set, one without: of 40 requests alternating
between them, each worker serving both, the 20 errors of the one were all in its own log directory and the 20 of the
other all in var/log. Thirty further requests to the site without the settings left the INI cache of the other
untouched and wrote theirs into var/cache/ini. With LogVarDir=var_log, CacheVarDir=var_cache,
INICacheDir=site and ExpiryDir=expiry in the global override, Velocity served the packed style sheets and scripts
from var_cache/<site>/cache/public/ and refused the other files there, the errors went to var_log/<site>/log, and
clearing and purging the INI cache, the content tag and all caches worked on var_cache while expiry.php stayed in
var/<site>/expiry/. The same with nginx and PHP-FPM.
What reads the logs
- Setup log (
var/log/setup.log, the installation runs): the errors and warnings of each step are counted in theerror.logandwarning.logeZDebug writes when the step begins, the log directory of the site withUseGlobalLogDir=disabled,var/logotherwise.setup.logitself stays invar/log. - Setup > System information names the log directory of the current site in the Storage card, warns when it or
var/logcannot be written, and finds the cronjob logs (cronjob*.log) invar/logand in the log directory of the site. It describes the siteaccess it runs in; the log directories of other sites are not listed. - Audit records (
audit.ini [AuditSettings] LogDir,log/auditby default) stay inside VarDir: they belong with the storage of the site, are kept for years and are checked against their hash chain, soLogVarDir,LogDirandUseGlobalLogDirdo not move them (aLogVarDiron a memory file system would lose them). An absolute[AuditSettings] LogDirmoves them. The old text files of 4.x names ([AuditCompatSettings] LegacyFiles) are written into the same directory, and an absoluteVarDiris used as it is. - The view counter cronjob (
updateviewcount) keepsupdateview.login the log directory of the site.
Cluster (DFS)
With
eZDFSFileHandlerthe files of the cache are kept in the cluster database and on the DFS mount under the same names as on disk, so underCacheVarDirthey are namedvar_cache/<site>/cache/.... The split cache table (MetaDataTableNameCache) takes every name that containsCacheDirand notStorageDir, which a name belowvar_cachedoes. Emptying the split cache table (deleteCacheFiles()) takes the names below the cache directory of the site,eZSys::cacheDirectory().index_cluster.phpserves the name the request asks for when the cluster database has it, but the web server only sends it the paths its rewrite rules name. Add the public caches belowCacheVarDirnext to those ofvar/:RewriteRule ^/var_cache/([^/]+/)?cache/(texttoimage|public)/.* /index_cluster.php [L]Velocity serves the public caches from the local disk, as it does below
var/.expiry.phpis a cluster file as well: its master copy is in the cluster database and on the mount, so a memory file system on the web server does not lose it, andExpiryDiris not needed for that. WithExpiryDir=expiryits name isvar/<site>/expiry/expiry.php, which is kept in the main table (ezdfsfile), not the cache table, and is not removed when the cache table is emptied.
Limits
- Compiled templates go to
<cache directory>/template/compiled/, derived once per process (eZTemplateCompiler::compilationDirectory()). A script or a preview that changes siteaccess after compiling a template keeps writing compiled templates into the cache directory of the first site. This was so before with aVarDirper site;CacheVarDirdoes not change it: the compiled file is named after the template it compiles, so it is only kept in another site's cache, never used in place of one of that site's own. Velocity removes the directory with the other globals after every request. - INI files read before the siteaccess is known (other than
site.ini) hold no setting of a siteaccess and stay in the sharedvar/cache/ini/. Front controllers and scripts read every INI file again after the change (eZINI::resetAllInstances( false )), and that copy is cached in the INI cache of the site. - Windows paths: an absolute path is one that starts with
/, as before forCacheDir.C:\...inCacheDir,LogDir,LogVarDir,CacheVarDirorExpiryDiris taken as a directory inside VarDir; on Windows use paths inside the installation.
Moving an existing multi-site setup to these settings
Installations that already kept logs and caches apart, with changes of their own to the kernel, can do the same with the settings:
- Logs in a tree of their own (
var_log/<site>/log):UseGlobalLogDir=disabledandLogVarDir=var_log. - Caches in a tree of their own (
var_cache/<site>/cache):CacheVarDir=var_cache. Rules the web server already has for/var_cache/keep working; Velocity serves the public caches itself. - An
expiry.phpkept outside the cache under another name: copy it tovar/<site>/expiry/expiry.phpbefore the first request and setExpiryDir=expiry. Without it, image aliases created before the last clear would count as current. - An INI cache per site:
INICacheDir=site. - Code that rewrote every
var/<site>/logpath of the installation is not needed and not provided: extensions that log througheZLogoreZSys::logDirectory()follow the settings; one that builds the path from VarDir and LogDir itself has to be changed to useeZSys::logDirectory().
For extension authors
eZSys::logDirectory()returns the log directory of the site, aseZSys::cacheDirectory()does for the cache; both followLogVarDirandCacheVarDir(eZSys::relocatedVarDirectory()). Build no log or cache path fromVarDiryourself. For the cache directory of another siteaccess, pass its settings toeZSys::cacheDirectoryOf( eZINI::instance( 'site.ini.append', <its settings directory> ) ), as the classic menu and the toolbar do. AVarDirthat starts with..is not relocated.eZDebug::instance()->logDirectory()returns where the debug logs go;eZLog::write()without a directory writes there, and so doeseZLog::write()with the directoryvar/log(the old default, which callers also pass explicitly). Any other directory given toeZLog::write(), and a file written intovar/logwithouteZLog, stays where it is.eZExpiryHandler::filePath()returns theexpiry.phpof the site.
Tests
eZSiteLogAndExpiryPathsTest(no database): the log directory inside VarDir and absolute;LogVarDirandCacheVarDir, relative and absolute, with VarDirvarand with an absolute VarDir; the debug logs andeZLogthere and back tovar/log;storage.login an absoluteLogDir;UseGlobalLogDir;ExpiryDirempty, relative, absolute and with a moved cache; timestamps that survive an emptied cache directory; the shared expiry instance after a change; the defaults, which give exactly the directories of before; aVarDirstarting with..or./; emptyCacheDirandLogDir; the cache directory of another siteaccess;eZDir::recursiveDelete()inside an absolute cache, log and expiry directory outside the root and nowhere beside them, anAllowedDeletionDirsentry that does not exist or is only a prefix (needs a system temp directory outside the installation).eZDirDeletionGuardTest(no database):eZDir::recursiveDelete()and the cache clears that go through it in every layout: the defaultvar/<site>/cache, aCacheVarDirinside the root and one that is a link outside it, an absoluteCacheDirandCacheVarDir; inside the cache and the cache directory as a whole, never its parent, the cache of another site or what is beside it; the codepage, template compile and override caches and a package import directory cleared with and withoutRenameBeforeDelete; aLogDirorCacheDirthat is or contains the installation allows nothing; a cache directory that does not exist allows nothing.eZSiteAccessSitePathsTest(writes siteaccesses of its own, no database):eZSiteAccess::change()to a site with the settings and then to one without, in the same process;LogVarDir,CacheVarDirand the INI cache in the moved cache; a directory the installation set, kept and restored; a new request without the globals Velocity removes;site.inicached in the INI cache of its own site and none left in that of the site before; the same site twice; the log directory without the function file ofeZUpdateDebugLogDirectory(); an INI file read before the change and read again into the cache of the site.eZSiteLogReadersTest(no database): the setup log counts the errors in the log directory of the site; the system report names that directory and finds the cronjob log there; audit records stay inside VarDir whateverLogVarDirandLogDirsay, also with an absolute VarDir;eZSiteAccess::resetSitePaths().expVelocityEnginesTest: the public caches belowCacheVarDirare served and nothing else there; an absolute path,..or other characters are not;./var_cache/is served asvar_cache; aCacheVarDirset for one siteaccess only is served too; the warm-up resets the paths before it sweeps the globals.
Related pages
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.